第11课-双向链表
单链表的局限
单链表的结点都只有一个指向下一个结点的指针
单链表的数据元素无法直接访问其前驱元素
单链表的改进
双向链表的定义
在单链表的结点中增加一个指向其前驱的pre指针
双向链表拥有单链表的所有操作
创建链表
销毁链表
获取链表长度
清空链表
获取第pos个元素操作
插入元素到位置pos
删除位置pos处的元素
NOTE:
教科书上大多是上面这样的四步操作,但是,由于有前面单链表的经验,我们应该把上图的1和2换一下顺序,3和4换一下顺序。当然不换也可以的,因为我们引入了两个辅助指针变量,但是,建议按照之前单链表的思想来链接节点,这是比较科学的做法,算是不成为的规定吧。
双向链表的新操作
获取当前游标指向的数据元素
将游标重置指向链表中的第一个数据元素
将游标移动指向到链表中的下一个数据元素
将游标移动指向到链表中的上一个数据元素
直接指定删除链表中的某个数据元素
DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node);
DLinkListNode* DLinkList_Reset(DLinkList* list);
DLinkListNode* DLinkList_Current(DLinkList* list);
DLinkListNode* DLinkList_Next(DLinkList* list);
DLinkListNode* DLinkList_Pre(DLinkList* list);
代码练兵场:
必知类型声明:
typedef struct _tag_DLinkList { DLinkListNode header; DLinkListNode* slider; int length; } TDLinkList;
typedef void DLinkList; typedef struct _tag_DLinkListNode { struct _tag_DLinkListNode* next; struct _tag_DLinkListNode* pre; }DLinkListNode;
链表的创建,和单链表、循环链表差不多:
DLinkList* DLinkList_Create() { TDLinkList* ret = (TDLinkList*)malloc(sizeof(TDLinkList)); if( ret != NULL ) { ret->length = 0; ret->header.next = NULL; ret->header.pre = NULL; ret->slider = NULL; } return ret; }
指针域先给NULL,长度置零。
链表的销毁,清除,获得长度:
void DLinkList_Destroy(DLinkList* list) { if(list!=NULL) { free(list); list=NULL; } } void DLinkList_Clear(DLinkList* list) { TDLinkList* sList = (TDLinkList*)list; if( sList != NULL ) { sList->length = 0; sList->header.next = NULL; sList->header.pre = NULL; sList->slider = NULL; } } int DLinkList_Length(DLinkList* list) { TDLinkList* sList = (TDLinkList*)list; int ret = -1; if( sList != NULL ) { ret = sList->length; } return ret; }
和之前的单链表、循环链表几乎一致,也比较简单,就不在赘述了。
链表的插入:
int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos) { TDLinkList* sList = (TDLinkList*)list; int ret = (sList != NULL) && (pos >= 0) && (node != NULL); int i = 0; if( ret ) { DLinkListNode* current = (DLinkListNode*)sList; DLinkListNode* next = NULL; //current->next != NULL保证了插入的pos大于链表此刻最大长度current指针的移动也不会出错 for(i=0; (i<pos) && (current->next != NULL); i++) { current = current->next; } next = current->next;//赋值辅助变量next,使其指向current->next node->next = next; current->next = node; node->pre = current; if( next != NULL )//next如果为NULL,证明插入到了末尾,需特殊考虑,末尾时刻next为NULL,故不应该执行下面语句当然,第一次插入的时候,既是末尾也是开头 { next->pre = node; } if( sList->length == 0 )//第一次插入时,node的前继应该置成NULL(覆盖之前的值),游标指向第一个节点 { node->pre = NULL; sList->slider = node; } sList->length++; }
获取链表的pos位置(和之前链表的并无不同,不再赘述):
DLinkListNode* DLinkList_Get(DLinkList* list, int pos) { TDLinkList* sList = (TDLinkList*)list; DLinkListNode* ret = NULL; int i = 0; if( (sList != NULL) && (0 <= pos) && (pos < sList->length) ) { DLinkListNode* current = (DLinkListNode*)sList; for(i=0; i<pos; i++) { current = current->next; } ret = current->next; } return ret; }
删除操作:
DLinkListNode* DLinkList_Delete(DLinkList* list, int pos) // O(n) { TDLinkList* sList = (TDLinkList*)list; DLinkListNode* ret = NULL; int i = 0; if( (sList != NULL) && (0 <= pos) && (pos < sList->length) ) { DLinkListNode* current = (DLinkListNode*)sList; DLinkListNode* next = NULL; for(i=0; i<pos; i++) { current = current->next; } ret = current->next;//ret指向要删除的节点 next = ret->next;//赋值next,使其指向删除节点的下一个节点 current->next = next;//链接新节点(后继) if( next != NULL )//删除不是最后一个时的情况 { next->pre = current;//链接新节点(前继) if(current==(DLinkListNode*)sList)//删除第一个,特殊考虑 { next->pre = NULL;//前继置空 } ret->pre = NULL;//删除之后,断开ret和前后的联系 ret->next = NULL; } else //如果删除的是最后一个 { ret->pre = NULL;//前继置空,如果不这样,删除的最后一个时,next是NULL,不能对NULL指针操作前继使其指向current } if( sList->slider == ret )//如果删除的是游标,游标后移 { sList->slider = next; } sList->length--;//长度减一 } return ret; } DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node) { TDLinkList* sList = (TDLinkList*)list; DLinkListNode* ret = NULL; int i = 0; if( sList != NULL ) { DLinkListNode* current = (DLinkListNode*)sList; for(i=0; i<sList->length; i++) { if( current->next == node ) { ret = current->next; break; } current = current->next; } if( ret != NULL ) { DLinkList_Delete(sList, i); } } return ret; }
删除操作做了一点改动,考虑到参考程序没有把删除节点的前继和后继链接断开,自己实现了这一步。
游标操作和循环链表几乎一致(不再赘述):
DLinkListNode* DLinkList_Reset(DLinkList* list) { TDLinkList* sList = (TDLinkList*)list; DLinkListNode* ret = NULL; if( sList != NULL ) { sList->slider = sList->header.next; ret = sList->slider; } return ret; } DLinkListNode* DLinkList_Current(DLinkList* list) { TDLinkList* sList = (TDLinkList*)list; DLinkListNode* ret = NULL; if( sList != NULL ) { ret = sList->slider; } return ret; } DLinkListNode* DLinkList_Next(DLinkList* list) { TDLinkList* sList = (TDLinkList*)list; DLinkListNode* ret = NULL; if( (sList != NULL) && (sList->slider != NULL) ) { ret = sList->slider; sList->slider = ret->next; } return ret; } DLinkListNode* DLinkList_Pre(DLinkList* list) { TDLinkList* sList = (TDLinkList*)list; DLinkListNode* ret = NULL; if( (sList != NULL) && (sList->slider != NULL) ) { ret = sList->slider; sList->slider = ret->pre; } return ret; }
main.c:
#include <stdio.h> #include <stdlib.h> #include "DLinkList.h" struct Value { DLinkListNode header; int v; }; int main(int argc, char *argv[]) { int i = 0; DLinkList* list = DLinkList_Create(); struct Value* pv = NULL; struct Value v1; struct Value v2; struct Value v3; struct Value v4; struct Value v5; v1.v = 1; v2.v = 2; v3.v = 3; v4.v = 4; v5.v = 5; DLinkList_Insert(list, (DLinkListNode*)&v1, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v2, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v3, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v4, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v5, DLinkList_Length(list)); for(i=0; i<DLinkList_Length(list); i++)//遍历双向链表,打印1 2 3 4 5 { pv = (struct Value*)DLinkList_Get(list, i); printf("%d\n", pv->v); } printf("\n"); DLinkList_Delete(list, DLinkList_Length(list)-1);//删除最后一个节点,数据5 DLinkList_Delete(list, 0);//删除第一个节点,数据1 for(i=0; i<DLinkList_Length(list); i++)//打印删除之后剩余的元素,打印2 3 4 { pv = (struct Value*)DLinkList_Next(list); printf("%d\n", pv->v); } printf("\n"); DLinkList_Reset(list);//游标复位 DLinkList_Next(list);//游标指向下一个,指向3 pv = (struct Value*)DLinkList_Current(list);//获取现在游标指向 printf("%d\n", pv->v);//打印 3 DLinkList_DeleteNode(list, (DLinkListNode*)pv);//删除 3 pv = (struct Value*)DLinkList_Current(list);//获取游标现在的指向,指向4 printf("%d\n", pv->v);//打印4 DLinkList_Pre(list);//游标前移,指向2 pv = (struct Value*)DLinkList_Current(list);//获取现在游标的指向 printf("%d\n", pv->v);//打印 2 printf("Length: %d\n", DLinkList_Length(list));//输出链表长度 DLinkList_Destroy(list);//销毁链表 return 0; }
小结
双向链表在单链表的基础上增加了指向前驱的指针
功能上双向链表可以完全取代单链表的使用
循环链表的Next,Pre和Current操作可以高效的遍历链表中的所有元素
欢迎加入作者的小圈子
扫描下方左边二维码加入QQ交流群,扫描下方右边二维码关注个人微信公众号并获取更多隐藏干货,QQ交流群:816747642 微信公众号:Crystal软件学堂
作者:Crystal软件学堂 bilibili视频教程地址:https://space.bilibili.com/5782182 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在转载文章页面给出原文连接。 如果你觉得文章对你有所帮助,烦请点个推荐,你的支持是我更文的动力。 文中若有错误,请您务必指出,感谢给予我建议并让我提高的你。 |