1、双向循环链表结点定义和函数声明
双向循环链表结点内部有2个指针prev和next分别指向前后的结点,结点定义代码如下:
typedefstructdlist{ intdata; structdlist*next; structdlist*prev; }dlist_t;
现在我们声明一些双向循环链表操作函数,代码如下:
externdlist_t *new_dlist_node(intdata); //新建一个结点 externdlist_t *dlist_add(dlist_t*list, intdata); //插入一个结点 externdlist_t *dlist_delete(dlist_t*list, intindex); //删除索引对应的结点(索引从0开始) externdlist_t *dlist_index_of(dlist_t*list, intindex); //查找索引对应的结点 externvoiddlist_destroy(dlist_t*list); //销毁双向循环链表 externvoiddlist_print(dlist_t*list); //打印链表数据
可以看到很多函数都会返回一个dlist_t类型的指针,其实这是头结点。很多时候为了书写方便我们会采用typedef定义自己的数据类型,结点定义里为什么next和prev指针可以那样写,参考我上一篇文章,后面会大量这样使用。
2、双向循环链表函数实现
为了更方便释放一个结点内存,我们定义了一个文件作用域静态函数free_dlist_node。
voidfree_dlist_node(dlist_t*node){ if(node == NULL){ return; } node->next = NULL; node->prev = NULL; free(node); node = NULL; }
该函数只负责释放内存的操作。
新建链表结点
dlist_t*new_dlist_node(intdata){ dlist_t*node = (dlist_t*)malloc(sizeof(dlist_t)); if(node == NULL){ returnNULL; } node->data = data; node->next = node; node->prev = node; returnnode; }
这里我们主要注意一点就是新建立的结点next和prev指针会初始化为指向自身,事实上双向循环链表头结点必须这样初始化才能更好的利用双向循环链表特性执行插入、删除和查询等操作。
插入结点
dlist_t *dlist_add(dlist_t *list, int data){ if(list== NULL){ returnNULL; } //新建一个结点 dlist_t *node = new_dlist_node(data); if(node == NULL){ returnlist; } //将结点插入双向循环链表 list->prev->next = node; //最后的结点next指针指向node node->next = list; //node的next指针指向头结点 node->prev = list->prev; //node的prev指针指向原尾结点 list->prev = node; //头结点的prev指针指向新尾结点 returnlist; }
这里list是传入的头结点,我们采用尾插法插入一个结点,最后返回头结点。这里需要注意:每次插入结点都要更新头结点的prev指针。
删除指定位置的结点
dlist_t *dlist_delete(dlist_t *list, int index){ if(list== NULL|| index < 0){ return list; } dlist_t *head = list; int list_index = 0; //删除链表头结点 if(index == 0){ //链表只有一个结点 if(head == list->next){ free_dlist_node(list); list= NULL; returnNULL; }else{ //链表有大于1个结点 head = head->next; //头结点往后移一个 head->prev = list->prev; //头结点的prev指向尾部结点 list->prev->next = head; //尾部结点的next指针指向头结点 free_dlist_node(list); //释放结点内存 returnhead; } } list= list->next; list_index++; //查询目标结点,通过检查当前结点是否是头结点判断是否已经把双线循环链表遍历了一遍 while(list_index < index && head != list){ list = list->next; list_index++; } //没有找到即将删除的结点 if(head == list){ returnhead; } //找到即将删除的结点 else{ list->prev->next = list->next; //目标结点的上一个结点的next指针指向目标结点的下一个结点 list->next->prev = list->prev; //目标结点的下一个结点的prev指针指向目标结点的上一个结点 free_dlist_node(list); //释放结点内存 returnhead; //返回头结点 } }
删除结点一定要注意:
判断被删除的结点是否是头结点
指定的位置是否超出了链表的长度。
查找指定位置的结点
dlist_t*dlist_index_of(dlist_t*list, intindex){ if(list== NULL|| index < 0){ return NULL; } //如果想要获取头结点,则直接返回头结点 if(index == 0){ return list; } dlist_t *head = list; list = list->next; intlist_index = 1; //遍历链表查找指定的索引,通过检查当前结点是否等于头结点判断是否已遍历完毕 while(list_index < index && list != head){ list_index++; list = list->next; } //没有找到索引对应的结点 if(list== head){ returnNULL; } //找到了索引对应的结点 returnlist; }
查找指定位置结点要注意几个地方:
是否是头结点
是否超出了链表长度
销毁链表
void dlist_destroy(dlist_t *list){ if(list== NULL){ return; } //如果只有一个结点 if(list->next == list){ free_dlist_node(list); return; } dlist_t *temp = list; list= list->next; while(list!= temp){ list= list->next; //遍历下一个结点 temp->prev->next = list; list->prev = temp->prev; temp->next = NULL; temp->prev = NULL; free(temp); temp = list; } free_dlist_node(list); }
打印链表数据
voiddlist_print(dlist_t*list){ if(list== NULL){ return; } dlist_t*head = list; printf("%d, ", list->data); list= list->next; while(list!= head){ printf("%d, ", list->data); list= list->next; } printf(" "); }
最后我们写个小程序验证一下双向循环链表函数实现是否正确。
#include
编译运行输出:
打印未处理过的完整链表 2, 3, 4, 5, 6, 7, 8, 9, 打印删除2个结点后的链表 3, 4, 5, 7, 8, 9, 第3个结点是:5 销毁链表
验证完全正确。
责任编辑:lq6
-
函数
+关注
关注
3文章
4304浏览量
62426 -
链表
+关注
关注
0文章
80浏览量
10547
原文标题:数据结构与算法篇-双向循环链表
文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论