前面中我们讲解了介绍了链表这种数据结构和单链表的基本有操作。数据结构:超详细的详解链表(上)——单链表_阿威昂的博客-CSDN博客
现在我们将延续链表,我们将介绍一种新的链表类型——带头双向循环链表。
这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。他的规律性更强,更容易实现。各种操作都是对应相通的。会一种其余的也都会了。
我们现在就来逐步分析这种链表。
构建链表的结构体成员变量。
typedef int LTdatatype; typedef struct Dlistnode { LTdatatype data;//数据域 struct Dlistnode* prev;//节点的上一个节点 struct Dlistnode* next;//节点的下一个节点 }dlnode;
//初始化
dlnode* DLInit();
//创建节点
dlnode* buydlnode(LTdatatype x);
//尾插节点
void dlistpushback(dlnode* head, LTdatatype x);//尾插节点
void dlistpopback(dlnode* head);
//头插节点
void dlistpushfront(dlnode* head, LTdatatype x);
//头删节点void dlistpopfront(dlnode* head);
//pos位置插入节点void dlistinsert(dlnode* pos, LTdatatype x);
//删除就是删除中间的节点
void dlisterase(dlnode* pos, LTdatatype x);
1.初始化链表
//初始化 dlnode* DLInit() { dlnode* head = buydlnode(0); head->next=head;//头节点下一节点是头节点 head->prev = head;//头节点上一个节点也是头节点 return head; }
2.创建节点
dlnode* buydlnode(LTdatatype x) { dlnode* node = (dlnode*)malloc(sizeof(dlnode));//申请一个空间 if (node == NULL) { perror("malloc filed"); exit(-1); } node->data = x;//新节点的数据域是x //创建单独一个节点,后面可以用头插尾插来连接 node->next = NULL; node->prev = NULL; return node; }
3.头插节点
void dlistpushfront(dlnode* head, LTdatatype x) { assert(head); dlnode* headnext = head->next;创建一个headnext表示头节点的下一个节点 dlnode* node = buydlnode(x); head->next = node; //先改左边 headnext->prev = node; node->prev = head; node->next = headnext; }
图解:
注意:链表中连接顺序就是先连接左边再连接右边。不然会连接失败,头插失败。
4.尾插节点
void dlistpushback(dlnode* head, LTdatatype x) { assert(head); dlnode* newnode = buydlnode(x); dlnode* tail = head->prev;//直接用头节点的上一个节点来表示最后链表中最后一个节点 //循环链表就可以不用遍历链表来得到最后一个节点 dlnode* cur; /*while (cur) { cur = cur->next;双向链表不适用 }*/ // 同样的连接顺序,先连接左边 tail->next = newnode; newnode->prev = tail; head->prev = newnode; newnode->next = head; }
注意:循环链表可以不用遍历链表来得到最后一个节点,就直接使用head->prev得到最后一个节点
5.头删节点
void dlistpopfront(dlnode* head) { assert(head); assert(head->next != head); dlnode* first = head->next; dlnode*second = first->next; //删除操作 head->next = second; second->prev = head; free(first); }
6.尾删节点
void dlistpopback(dlnode* head) { assert(head); assert(head->next != head); dlnode* tail = head->prev; dlnode* tailprev = tail->prev; tailprev->next = head; head->prev = tailprev; free(tail); }
7.中间插入节点
void dlistinsert(dlnode* pos, LTdatatype x) { assert(pos); dlnode* newnode = buydlnode(x); dlnode* posprev = pos->prev; //插入操作 posprev->next = newnode; newnode->prev = posprev; pos->prev = newnode; newnode->next = pos; //可以来表示头插(dlistinsert(head->next)),尾插(pos之前插入)(dlistinsert(head)) //顺序 }
我们发现这个插入节点我们可以用来头插尾插操作,表示头插(dlistinsert(head->next)),尾插(pos之前插入)(dlistinsert(head))
8.删除节点
void dlisterase(dlnode* pos, LTdatatype x) { assert(pos); dlnode* posprev = pos->prev; dlnode* posnext = pos->next; posprev->next = posnext; posnext->prev = posprev; free(pos); //同样的也可表示头删(dlisterase(head->next)),尾删(dlistinsert(head->prev)) }
同样的我们可以用来实现头删尾删操作
通过插入节点或删除节点我们可以快速的进行构建链表,同时我们也可以利用这个进行不同的操作,头插尾插,头删尾删操作。
尾结:
链表这种数据结构其实也不难,关键在于画图,发现其中的规律。也欢迎大佬们再评论区里讨论。