赞
踩
双向带头循环链表是链表结构最复杂,但使用最方便的链表。
[图中phead表示链表的头节点(哨兵);
d1,d2等表示链表存储的数据;
d1,d2等左侧的方框存储是指向该节点的上一个节点的指针(prev),右侧方框存储指向该节点的下一个的指针(next)]
双向带头循环链表的每一个节点的指针域都有俩个指针,一个指针(prev)指向该节点的前一个节点,一个指针(next)指向它的后一个节点。
解决了单链表只能向后访问,不能向前访问的问题
双向带头循环链表的头节点(哨兵)位于第一个存储数据的链表的前一个结点
双向带头循环链表有一个头节点(哨兵),该节点无论链表是否为空它都存在
头节点(哨兵)的数据域一般不存储数据或者存储链表的信息(有几个节点等)
双向带头循环链表的头节点(哨兵)指针域的也有两个指针,且指向前一个节点的指针(prev)指向链表的最后一个节点,指向下一个节点的指针(next)指向链表的第一个节点
创建一个头文件(SList.h)两个源文件(SList.c和test.c)
上图包含了以下3个操作
1.库函数的头文件的包含:
stdio.h:输入/输出等函数
stdlib.h:动态内存申请
assert.h:报错函数assert
2.给链表节点的数据域的数据类型重命名
为什么要重命名呢?
这是为了以后如果改变了LTNoed结构体中数据存储的类型时,
不用到处改函数参数等地方的数据类型,只要改typedef后的int 为对应要改成的数据类型就可以。
3.链表节点结构体定义和重命名
参数设计:
无需参数,将返回值赋值给一个指针,这个指针就成为链表的phead。
LTNode*phead:
上面说了因为头指针(phead)不会改变,所以传一级指针phead即可
LTDaType x:
要插入的数据
链表为空的时候也不需要像单链表那样特殊考虑,这也是双向带头循环链表的好处
随机插入的pos要配合查找函数的返回值使用·
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<assert.h> typedef int LTDateType; typedef struct LTNode { LTDateType data; struct LTNode* next; struct LTNode* prev; }LTNode; //初始化链表 LTNode* ListInit(void); //打印链表 void ListPrint(LTNode* phead); //尾插 void ListPushBack(LTNode* phead, LTDateType x); //头插 void ListPushFront(LTNode* phead, LTDateType x); //尾删 void ListPopBack(LTNode* phead); //头删 void ListPopFront(LTNode* phead); //查找x,找到了返回指向x的结构指针,找不到返回NULL LTNode* ListFind(LTNode* phead, LTDateType x); //在pos之前插入数据 void ListInsert(LTNode* phead, LTNode* pos, LTDateType x); //删除pos指向的节点 void ListEase(LTNode* phead, LTNode* pos); //销毁链表 void ListDestory(LTNode* phead);
#define _CRT_SECURE_NO_WARNINGS #include"List.h" LTNode* ListInit() { //哨兵位头节点 LTNode* phead = (LTNode*)malloc(sizeof(LTNode));//为头结点申请空间 phead->data = 0;//不存储链表数据(链表的长度等)时也可不初始化 phead->next = phead;//链表为空时头结点的next指向自己 phead->prev = phead;//链表为空时头结点的prev也指向自己 return phead;//返回被一个节点(phead)接收 } void ListPushBack(LTNode* phead, LTDateType x) { LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//为新节点申请空间 if (newnode == NULL)//如果为新节点申请空间失败 { printf("malloc失败"); exit(-1);//结束程序,-1表示程序非正常结束 } LTNode* tail = phead->prev;//tail指向链表的最后一个节点 tail->next = newnode;//让新节点成为原尾节点的下一个节点 newnode->prev = tail;//让原尾节点成为新节点的上一个节点 phead->prev = newnode;//更新头结点中存储的尾节点 newnode->next = phead;//让新节点的下一个节点为头结点(phead) newnode->data = x;//存储数据 } void ListPrint(LTNode* phead) { LTNode* cur = phead->next;//将链表的第一个节点赋值给cur while (cur != phead)//因为双向带头循环链表没有指向NULL的指针了,而且链表的尾节点的next指向头结点(phead) //所以遍历链表结束的条件为cur==phead { printf("%d ", cur->data);//打印节点数据域的数据 cur = cur->next;//让cur指向下一个节点 } } //头插 void ListPushFront(LTNode* phead, LTDateType x) { LTNode* cur = phead->next;//让cur指向链表的第一个节点 LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//为新节点申请空间 if (newnode == NULL)//如果为新节点申请空间失败 { printf("malloc失败"); exit(-1);//结束程序,-1表示程序非正常结束 } phead->next = newnode;//让头结点的下一个节点为新节点 newnode->next = cur;//让新节点的下一个节点为原链表的第一个节点 newnode->prev = phead;//让新节点的上一个节点为头结点 newnode->data = x; cur->prev = newnode;//让原链表的第一个节点的上一个节点为新节点 } //尾删 void ListPopBack(LTNode* phead) { assert(phead->next != phead);//链表为空时不能再删除 LTNode* tail = phead->prev;//让tail指向尾节点 tail->prev->next = phead;//让尾节点(tail)的前一个节点的next指向头结点, phead->prev = tail->prev;//让删除之前的尾节点的前一个节点成为新的尾节点 free(tail);//释放删除之前的尾节点 } //头删 void ListPopFront(LTNode* phead) { assert(phead->next != phead);//链表为空时不能再删除 LTNode* cur = phead->next;//让cur指向链表的第一个节点 phead->next = cur->next;//让头节点的下一个节点为原链表第一个节点的下一个节点(原链表的第二个节点) cur->next->prev = phead;//让原链表的第二个节点的前一个节点为头结点 free(cur);//释放原链表第一个节点 } //查找x,找到了返回指向x的结构指针,找不到返回NULL LTNode* ListFind(LTNode* phead, LTDateType x) { LTNode* cur = phead->next;//让cur指向链表的第一个节点 while (cur != phead)//因为双向带头循环链表没有指向NULL的指针了,而且链表的尾节点的next指向头结点(phead) //所以遍历链表结束的条件为cur==phead { if (cur->data == x) { return cur;//找到了就直接返回 } else { cur = cur->next;//让cur指向下一个节点 } } return NULL;//找不到就返回NULL } //在pos之前插入数据 void ListInsert(LTNode** phead, LTNode* pos, LTDateType x) { LTNode* newnode = (LTNode*)malloc(sizeof(LTNode)); if (newnode == NULL)//如果为新节点申请空间失败 { printf("malloc失败"); exit(-1);//结束程序,-1表示程序非正常结束 } newnode->data = x;//存放数据 newnode->next = pos;//让新节点的下一个节点为pos指向的节点 newnode->prev = pos->prev;//让新节点的上一个节点为pos指向节点的前一个节点 pos->prev->next = newnode;//pos指向节点的上一个节点的下一个节点为新节点 pos->prev = newnode;//让新节点成为pos指向节点的上一个节点 } //删除pos指向的节点 void ListEase(LTNode* phead, LTNode* pos) { assert(pos != phead);//链表为空时再不能删除 pos->prev->next = pos->next;//让pos指向节点的前一个节点的next指向pos指向节点的下一个节点 pos->next->prev = pos->prev;//让pos指向节点的下一个节点的prev指向pos指向节点的上一个节点 free(pos);//释放pos指向节点 } //销毁链表 void ListDestory(LTNode* phead) { LTNode* cur = phead->next;//让cur指向链表的第一个节点 LTNode* tmp = phead->next; while (cur != phead) { tmp = cur->next;//tmp指向cur的下一个节点,防止cur被释放了,找不到下一个节点了 free(cur); cur = tmp;//让cur指向下一个节点 } phead->prev = phead;//链表的所有节点都被释放后,头节点的prev要指向自己,让链表下一次使用时不会出错 phead->next = phead;//链表的所有节点都被释放后,头节点的next也要指向自己 }
以上就是所有内容了,如果对你有帮助的话,可以点个赞支持一下!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。