上一篇的单链表还存在许多的不方便,那么这一篇就来介绍一下更实用、更方便的链表吧。
目录
链表的分类
初始化链表
增加数据
尾插
头插
删除数据
尾删
头删
随机插入删除
随机插入
随机删除
结尾总结
链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构上一篇单链表博客介绍的就是无头单向非循环链表,这一篇就来介绍带头双向循环链表。
初始化链表
prev指向上一个节点,next指向下一个节点。
初始化函数的返回类型我们定义成结构体指针,如果返回值定义成void类型的话,那就需要传入二级指针。
BuyListNode函数用来申请空间,那么链表是带头的链表,初始化就是申请一块空间,也就是哨兵位,不存放有效数据,它的prev和next都指向自己因为是循环,prev和next意义也就是双向。
那BuyListNode函数
这样一来,哨兵位就写好了。
增加数据 尾插
头插这个尾插就不用传入二级指针,上一篇插入为什么会传入二级指针是因为需要改变链表。那传入的是哨兵位的地址,我们不需要改变哨兵位,改变的是哨兵位指向的内容,但是传入的是结构体的指针,这样就能插入删除。
为了测试,再写一个打印函数
相比之下这个链表就要方便很多,不需要再一步步找尾节点,头节点的上一个直接就是尾。
看到这里就体现出这种链表的优势,插入都不需要写空链表的情况,这样写就可以包含空链表的情况。
删除数据 尾删
头删
测试一下也没有问题。
随机插入删除 随机插入
随机删除
既然随机插入写好了,那头插和尾插也就可以复用了。
尾插复用
头插复用
同样,头删和尾删也可以复用
尾删复用
头删复用
结尾总结
这就是双向带头循环链表的魅力,虽然是链表的进阶,那么结构就会更复杂,但是在 *** 作的时候可以感觉到非常舒服, *** 作非常方便,尾插不需要一个一个找,而且提到复用,整个链表只需要把随即插入和删除写好,整个链表也就完成了,如果写的熟练的话5分钟就可以写一个链表。
那么所有的代码还是放到了结尾。
List.h文件
#include
#include #include typedef int LTDataType; typedef struct ListNode { struct List* prev; struct List* next; LTDataType data; }LTNode; //初始化 LTNode* ListInit(); //打印 void ListPrint(LTNode* phead); //尾插/头插/尾删/头删 void ListPushBack(LTNode* phead, LTDataType x); void ListPushFront(LTNode* phead, LTDataType x); void ListPopBack(LTNode* phead); void ListPopFront(LTNode* phead); //在pos位置之前插入x void ListInsert(LTNode* pos, LTDataType x); //删除pos位置 void ListErase(LTNode* pos); List.c文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"List.h" LTNode* BuyListNode(LTDataType x) { LTNode* newnode = (LTNode*)malloc(sizeof(LTNode)); if (newnode == NULL) { perror("malloc"); exit(-1);//直接退出程序 } newnode->data = x; newnode->prev = NULL; newnode->next = NULL; return newnode; } LTNode* ListInit() { LTNode* phead = BuyListNode(-1); phead->prev = phead; phead->next = phead; return phead; } void ListPrint(LTNode* phead) { assert(phead); LTNode* cur = phead->next; while (cur != phead) { printf("%d ", cur->data); cur = cur->next; } printf("\n"); } void ListPushBack(LTNode* phead, LTDataType x) { assert(phead); //LTNode* newnode = BuyListNode(x); //LTNode* tail = phead->prev; //tail->next = newnode; //newnode->prev = tail; //newnode->next = phead; //phead->prev = newnode; ListInsert(phead, x); } void ListPushFront(LTNode* phead, LTDataType x) { assert(phead); //LTNode* newnode = BuyListNode(x); //LTNode* next = phead->next; //newnode->next = next; //next->prev = newnode; //newnode->prev = phead; //phead->next = newnode; ListInsert(phead->next, x); } void ListPopBack(LTNode* phead) { assert(phead); assert(phead->next != phead);//证明链表为空,就不要删了 //LTNode* tail = phead->prev; //LTNode* tailPrev = tail->prev; //free(tail); //tailPrev->next = phead; //phead->prev = tailPrev; ListErase(phead->prev); } void ListPopFront(LTNode* phead) { assert(phead); assert(phead->next != phead); //LTNode* pos = phead->next; //LTNode* next = pos->next; //phead->next = next; //next->prev = phead; //free(pos); ListErase(phead->next); } //在pos位置之前插入x void ListInsert(LTNode* pos, LTDataType x) { assert(pos); LTNode* prev = pos->prev; LTNode* newnode = BuyListNode(x); newnode->next = pos; pos->prev = newnode; prev->next = newnode; newnode->prev = prev; } //删除pos位置 void ListErase(LTNode* pos) { assert(pos); LTNode* prev = pos->prev; LTNode* next = pos->next; prev->next = next; next->prev = prev; free(pos); }
同样如果想变得更好看可以自己手写一个菜单,这一篇就介绍到这里。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)