前回の記事では、リンク リストの構造を理解して、ヘッドレスの一方向非循環リンク リストを実現しましたが、次に、もう 1 つのよく使用されるリンク リスト構造である、主要な双方向循環リンク リストを実装しました。一方向リンクリストがまだ理解できない場合は、この記事を読んでください(メッセージ 7 件) [データ構造とアルゴリズム] 線形リストのリンクリストを知り、一方向リンクリストを実現する_Xiaowang Xuecode Blog-CSDN Blog
目次
序文
主要な双方向循環リンク リストは、リンク リストの中で最も複雑な構造です。実装する主な機能は、先頭の挿入と末尾の挿入、先頭の削除と末尾の削除、初期化、印刷、挿入する位置の指定です。またはノードを削除し、ノードを検索し、リンクされたリストやその他の機能を破棄します。
1. 主要な双方向循環リンクリストは何ですか?
写真が示すように:
2. 最先端の双方向循環リンクリストの実現
1. 構成と実装する機能
構造は次のとおりです。
typedef struct DSLDataType;
//定义双向链表的结构体
//双向链表每一个节点都有一个前驱节点和后继节点,可以根据节点获得其前后的节点
typedef struct DSListNode {
struct DSListNode* prev;//前驱节点
struct DSListNode* next;//后继节点
int value;//数据域
}DSLNode;//重定义
一方向リンク リストと比較すると、前のノード アドレスを指すポインター フィールド prev が 1 つ増えています。
関数を実装する関数:
//初始化
DSLNode*ListInit();
//打印链表
void ListPrint(DSLNode* ps);
//尾部插入
void ListPushBack(DSLNode* ps, int data);
//头部插入
void ListPushFront(DSLNode* ps, int data);
//尾部删除
void ListPopBack(DSLNode* ps);
//头部删除
void ListPopFront(DSLNode* ps);
//判空
bool ListEmpty(DSLNode* ps);
//返回链表节点个数
int Listsize(DSLNode* ps);
//寻找指定元素,返回对应的节点
DSLNode* ListFind(DSLNode* ps, int data);
//在pos之前插入节点
void ListInsert(DSLNode* pos, int data);
//删除pos位置结点
void ListEarse(DSLNode* pos);
//摧毁链表
void ListDestory(DSLNode* ps);
2. リンクリストを初期化して印刷します。
//对于函数的实现
//初始化
DSLNode* ListInit() {
//初始化创建链表
//创建新节点
DSLNode* num = (DSLNode*)malloc(sizeof(DSLNode));
//判断是否创建成功
if (num == NULL) {
perror("malloc fail\n");
exit(-1);
}
num->prev = num;
num->next = num;//作为头节点,前驱和后驱结点都指向自己
return num;
}
//打印链表
void ListPrint(DSLNode* ps) {
assert(ps);//断言
DSLNode* cur = ps->next;
printf("phead->");
while (cur != ps) {//双向链表,回到ps,就表示转了一圈
printf("%d->", cur->value);
cur = cur->next;
}
printf("NULL\n");
}
3. ヘッドプラグとテールプラグ
テールプラグを図に示します。
コードは以下のように表示されます:
DSLNode* CreatNode(int data) {//创建新节点
DSLNode* cur = (DSLNode*)malloc(sizeof(DSLNode));
if (cur == NULL) {
perror("malloc fail\n");
exit(-1);
}
cur->next = cur->prev = NULL;
cur->value = data;
}
//尾部插入
void ListPushBack(DSLNode* ps, int data) {
assert(ps);//断言
DSLNode* newnode = CreatNode(data);
DSLNode* tail = ps->prev;//把头节点之前的那个地址给tail
tail->next = newnode;//将ps之前的那个数值,实际上是这个双向链表的最后一个元素的位置,的next指针指向新节点
newnode->prev = tail;//新节点前后为 tail和ps
newnode->next = ps;
ps->prev = newnode;//tail的两个指针域都指向完成,就只剩下ps的前驱指针
}
ヘッドプラグは図に示されています。
コードは次のとおりです。
//头部插入
void ListPushFront(DSLNode* ps, int data) {
assert(ps);
//头部插入就是将ps之后的一个地址给临时变量tail
DSLNode* tail = ps->next;
DSLNode* newnode = CreatNode(data);//创建新节点
ps->next->prev = newnode;
newnode->next = ps->next;
newnode->prev = ps;
ps->next = newnode;
}
4. 先頭削除と末尾削除
末尾の削除を図に示します。
コードは以下のように表示されます:
//判空
bool ListEmpty(DSLNode* ps) {
assert(ps);//断言
return ps->next == ps;//如果是只有一个ps头节点,则表示为空,返回true,否则返回false
}
//尾部删除
void ListPopBack(DSLNode* ps) {
assert(ps);//断言
assert(!ListEmpty(ps));//先判断是否为空
DSLNode* cur = ps->prev;
//将cur的next值为NULL
ps->prev = ps->prev->prev;
ps->prev->next = ps;
//这是直接略过尾部最后一个元素
//跳过ps之前的一个节点,先让其左右节点相连,然后释放这个地址
free(cur);
cur = NULL;
}
図に示すように、ヘッダーが削除されます。
コードは以下のように表示されます:
//头部删除
void ListPopFront(DSLNode* ps) {
assert(ps);
assert(!ListEmpty(ps));
DSLNode* cur = ps->next;
//头删 是将头节点之后的第一个节点删除释放空间
ps->next = ps->next->next;
ps->next->prev = ps;
free(cur);
cur = NULL;
}
5. ノードの数を見つけて返します。
検索: データ値フィールドに従ってノードを返し、最初に見つかったノードを返します。そうでない場合は NULL を返します。
//返回链表节点个数
int Listsize(DSLNode* ps) {
assert(ps);
int count = 0;
DSLNode* cur = ps->next;
while (cur != ps) {
count++;
cur = cur->next;
}
return count;
}
//查找
DSLNode* ListFind(DSLNode* ps, int data) {
assert(ps);
DSLNode* cur = ps->next;
while (cur != ps) {
if (cur->value == data) {
return cur;
}
}
return NULL;//NULL表示不存在
}
6. pos 位置の前にノードを挿入します
写真が示すように:
コードは以下のように表示されます:
//插入节点
void ListInsert(DSLNode* pos, int data) {
assert(pos);
//pos三种可能
//1.空链表
//2.只有一个节点
DSLNode* cur = pos->prev;
//双向链表可以直接找到pos之前的位置,单向链表只能进行循环
DSLNode* newnode = CreatNode(data);
pos->prev = newnode;//把新节点newnode的位置给pos的prev
newnode->prev = cur;//cur表示的是创建new节点之前的时候pos之前的结点
cur->next = newnode;
newnode->next = pos;
//另一种不使用cur的方法
//newnode->next = pos;
//pos->prev->next = newnode;
//newnode->prev = pos->prev;
//pos->prev = newnode;
}
7. 指定したposノードを削除します
写真が示すように:
コードは以下のように表示されます:
//删除指针
void ListEarse(DSLNode* pos) {
assert(pos);
DSLNode* cur = pos->prev;
DSLNode* tail = pos->next;
cur->next = tail;//两者相互指向,最后释放pos空间
tail->prev = cur;
free(pos);
pos = NULL;
}
8. リンクリストを破棄する
2 つの方法、セカンダリ ポインタを直接使用することも、プライマリ ポインタを 1 つずつ空きと NULL で使用することもできます。
//摧毁链表
void ListDestory(DSLNode* ps) {
//可以设计二级指针直接将ps地址滞空为NULL
//这里还是使用一级指针
assert(ps);
DSLNode* cur = ps->next;
while (cur != ps) {
DSLNode* tail = cur->next;//这个地方就是一个精彩的地方
free(cur);//使用临时变量tail得到cur的下一个地址,然后再free cur
cur = tail;//tail这个时候就是cur 的下一个结点
}
free(ps);
ps = NULL;
}
void ListDestory2(DSLNode** ps) {
assert(ps);
free(ps);//二级指针直接free
*ps = NULL;
}
3. 完全なコード
1.DSLinkList.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<assert.h>
#include<stdbool.h>
typedef struct DSLDataType;
//定义双向链表的结构体
//双向链表每一个节点都有一个前驱节点和后继节点,可以根据节点获得其前后的节点
typedef struct DSListNode {
struct DSListNode* prev;//前驱节点
struct DSListNode* next;//后继节点
int value;//数据域
}DSLNode;//重定义
//创建头节点,并将tail和head都指向第一个节点
struct DSList {
struct DSListNode* tail;
struct DSListNode* head;
unsigned int len;//表示链表的长度
};
//初始化
DSLNode*ListInit();
//打印链表
void ListPrint(DSLNode* ps);
//尾部插入
void ListPushBack(DSLNode* ps, int data);
//头部插入
void ListPushFront(DSLNode* ps, int data);
//尾部删除
void ListPopBack(DSLNode* ps);
//头部删除
void ListPopFront(DSLNode* ps);
//判空
bool ListEmpty(DSLNode* ps);
//返回链表节点个数
int Listsize(DSLNode* ps);
//寻找指定元素,返回对应的节点
DSLNode* ListFind(DSLNode* ps, int data);
//在pos之前插入节点
void ListInsert(DSLNode* pos, int data);
//删除pos位置结点
void ListEarse(DSLNode* pos);
//摧毁链表
void ListDestory(DSLNode* ps);
2.DSLinkList.c
#define _CRT_SECURE_NO_WARNINGS
#include"DSLinkList.h"
//对于函数的实现
//初始化
DSLNode* ListInit() {
//初始化创建链表
//创建新节点
DSLNode* num = (DSLNode*)malloc(sizeof(DSLNode));
//判断是否创建成功
if (num == NULL) {
perror("malloc fail\n");
exit(-1);
}
num->prev = num;
num->next = num;
return num;
}
//打印链表
void ListPrint(DSLNode* ps) {
assert(ps);//断言
DSLNode* cur = ps->next;
printf("phead->");
while (cur != ps) {//双向链表,回到ps,就表示转了一圈
printf("%d->", cur->value);
cur = cur->next;
}
printf("NULL\n");
}
DSLNode* CreatNode(int data) {//创建新节点
DSLNode* cur = (DSLNode*)malloc(sizeof(DSLNode));
if (cur == NULL) {
perror("malloc fail\n");
exit(-1);
}
cur->next = cur->prev = NULL;
cur->value = data;
}
//尾部插入
void ListPushBack(DSLNode* ps, int data) {
assert(ps);//断言
DSLNode* newnode = CreatNode(data);
DSLNode* tail = ps->prev;//把头节点之前的那个地址给tail
tail->next = newnode;//将ps之前的那个数值,实际上是这个双向链表的最后一个元素的位置,的next指针指向新节点
newnode->prev = tail;//新节点前后为 tail和ps
newnode->next = ps;
ps->prev = newnode;//tail的两个指针域都指向完成,就只剩下ps的前驱指针
}
//头部插入
void ListPushFront(DSLNode* ps, int data) {
assert(ps);
//头部插入就是将ps之后的一个地址给临时变量tail
DSLNode* tail = ps->next;
DSLNode* newnode = CreatNode(data);//创建新节点
ps->next->prev = newnode;
newnode->next = ps->next;
newnode->prev = ps;
ps->next = newnode;
}
//判空
bool ListEmpty(DSLNode* ps) {
assert(ps);//断言
return ps->next == ps;//如果是只有一个ps头节点,则表示为空,返回true,否则返回false
}
//返回链表节点个数
int Listsize(DSLNode* ps) {
assert(ps);
int count = 0;
DSLNode* cur = ps->next;
while (cur != ps) {
count++;
cur = cur->next;
}
printf("\n");
return count;
}
//尾部删除
void ListPopBack(DSLNode* ps) {
assert(ps);//断言
assert(!ListEmpty(ps));//先判断是否为空
DSLNode* cur = ps->prev;
//将cur的next值为NULL
ps->prev = ps->prev->prev;
ps->prev->next = ps;
//这是直接略过尾部最后一个元素
//跳过ps之前的一个节点,先让其左右节点相连,然后释放这个地址
free(cur);
cur = NULL;
}
//头部删除
void ListPopFront(DSLNode* ps) {
assert(ps);
assert(!ListEmpty(ps));
DSLNode* cur = ps->next;
//头删 是将头节点之后的第一个节点删除释放空间
ps->next = ps->next->next;
ps->next->prev = ps;
free(cur);
cur = NULL;
}
//查找
DSLNode* ListFind(DSLNode* ps, int data) {
assert(ps);
DSLNode* cur = ps->next;
while (cur != ps) {
if (cur->value == data) {
return cur;
}
}
return NULL;//NULL表示不存在
}
//插入节点
void ListInsert(DSLNode* pos, int data) {
assert(pos);
//pos三种可能
//1.空链表
//2.只有一个节点
DSLNode* cur = pos->prev;
//双向链表可以直接找到pos之前的位置,单向链表只能进行循环
DSLNode* newnode = CreatNode(data);
pos->prev = newnode;//把新节点newnode的位置给pos的prev
newnode->prev = cur;//cur表示的是创建new节点之前的时候pos之前的结点
cur->next = newnode;
newnode->next = pos;
//另一种不使用cur的方法
//newnode->next = pos;
//pos->prev->next = newnode;
//newnode->prev = pos->prev;
//pos->prev = newnode;
}
//删除指针
void ListEarse(DSLNode* pos) {
assert(pos);
DSLNode* cur = pos->prev;
DSLNode* tail = pos->next;
cur->next = tail;//两者相互指向,最后释放pos空间
tail->prev = cur;
free(pos);
pos = NULL;
}
//摧毁链表
void ListDestory(DSLNode* ps) {
//可以设计二级指针直接将ps地址滞空为NULL
//这里还是使用一级指针
assert(ps);
DSLNode* cur = ps->next;
while (cur != ps) {
DSLNode* tail = cur->next;
free(cur);
cur = tail;
}
free(ps);
ps = NULL;
}
void ListDestory2(DSLNode** ps) {
assert(ps);
free(ps);
*ps = NULL;
}
3.テスト.c
#define _CRT_SECURE_NO_WARNINGS
#include"DSLinkList.h"
void test()
{
DSLNode* phead = ListInit();//初始化
ListPushBack(phead, 1);
ListPushBack(phead, 2);
ListPushBack(phead, 3);
ListPushBack(phead, 4);
ListPushBack(phead, 5);//检验尾插
ListPrint(phead);//打印
ListPushFront(phead, 10);
ListPushFront(phead, 20);
ListPushFront(phead, 30);
ListPushFront(phead, 40);
ListPushFront(phead, 50);//检验头插
ListPrint(phead);//打印
printf("%d\n", Listsize(phead));//检验返回结点个数
ListPopBack(phead);
ListPopBack(phead);
ListPopBack(phead);
ListPopBack(phead);
ListPopBack(phead);//尾删
ListPopFront(phead);
ListPopFront(phead);
ListPopFront(phead);
ListPopFront(phead);
ListPopFront(phead);//头删
ListPrint(phead);//打印
ListInsert(phead->next, 10);//pos指定结点之前插入newnode新节点
ListPrint(phead);//打印
}
int main()
{
test();
return 0;
}
要約する
この記事では、リンク リストの中で最も複雑な先頭の双方向循環リンク リスト、ポイント、リンク リストの破棄、その他の関数の使用法とコード実装を主に説明します。そして、誰もがよりよく理解できるように、完全なコードを含む、対応する機能の流れを説明する黒板図を作成しました。
次に、スタックとキューについて学びます。この記事の欠点について、1 つか 2 つ指導していただき、新しい章の学習を始めましょう。!!