本篇系上篇链表的补充,对双向带头循环链表进行模拟实现
tips:虽然听着比无头单向不循环链表复杂,但由于结构特点,模拟实现时是比极简形式的单链表容易许多的
首先我们还是先来看看双向链表的结点构成:
typedef int LTDatatype; typedef struct ListNode { struct ListNode *prev; struct ListNode *next; LTDatatype val; }ListNode;
我们可以看到,其余单向链表的区别即是不仅记录了后续结点,还有个前驱指针记录前一个结点;并且头指针的前驱指针是指向尾结点,尾结点的后继指针指向头结点,从而构成循环结构。
由于我们实现的为带头的双向链表,所以需要建立一个带“哨兵卫”的链表结点。
有两种方式:一是直接传结点进行初始化时将哨兵卫建立,二是将哨兵卫建立后返回该“哨兵卫”结点
tips:哨兵卫是不存储有效数据的,只用来存储头结点的地址
初始化链表:init_list()
ListNode* ListInit() { ListNode *phead = buy_newnode(0); phead->next = phead; phead->prev = phead; return phead; }
我们采用的是第二种方式,创建后返回接收即可。(这样可以避免传双指针的麻烦)
tips:我们可以看到,初始化时,前驱指针与后继指针都指向自己本身即构成了循环
好了,初始化链表之后我们一起来看看双向链表的增删查改吧
增:push_back() & push_front()
void push_back(ListNode *phead,LTDatatype x) { assert(phead); ListNode *tail = phead->prev; ListNode *newnode = buy_newnode(x); tail->next = newnode; newnode->prev = tail; newnode->next = phead; phead->prev = newnode; }
void push_front(ListNode *phead,LTDatatype x) { assert(phead); ListNode *next = phead->next; ListNode *newnode = buy_newnode(x); newnode->next = next; next->prev = newnode; phead->next = newnode; newnode->prev = phead; }
双向链表有了前驱和后继两个指针后,我们可以看到进行插入的步骤相差不多,只是区别于在哪里进行链接了。尾插时找尾也更便捷了,效率更高。
接下来就是删除:pop_back() & pop_front()
void pop_back(ListNode *phead) { assert(phead); assert(phead->next != phead); ListNode *tail = phead->prev; ListNode *prev = tail->prev; phead->prev = prev; prev->next = phead; free(tail); tail = NULL; printf("尾删成功!\n"); }
void pop_front(ListNode *phead) { assert(phead); assert(phead->next != phead); ListNode *first = phead->next; ListNode *second = first->next; phead->next = second; second->prev = phead; free(first); first = NULL; printf("头删成功!\n"); }
删除也是找到对应位置,断开原链接,将删除结点的前后结点链接起来即可
查找、随机删除与随机插入:find() & erase() & insert()
ListNode* find(ListNode *phead,LTDatatype x) { assert(phead); assert(phead->next != phead); ListNode *cur = phead->next; while(cur != phead) { if(cur->val == x) return cur; cur = cur->next; } printf("未找到该数据!\n"); return NULL; }
查找即遍历链表,找到结点即返回,找不到返回空指针
void insert(ListNode *pos,LTDatatype x) { assert(pos); ListNode *prev = pos->prev; ListNode *newnode = buy_newnode(x); prev->next = newnode; newnode->prev = prev; newnode->next = pos; pos->prev = newnode; }
void erase(ListNode **pos) { if(*pos == NULL) { printf("该结点不存在!\n"); return ; } ListNode *prev = (*pos)->prev; ListNode *next = (*pos)->next; free(*pos); *pos = NULL; prev->next = next; next->prev = prev; printf("删除成功\n"); }
tips:随机插入与随机删除的代码其实就可以复用到头插尾插以及头删尾删上,头插即在第一个位置插入,尾插即在最后一个位置插入
销毁链表:clear();
void clean(ListNode **pphead) { assert(pphead); ListNode *cur = (*pphead)->next; ListNode *next = cur->next; while(cur != *pphead) { free(cur); cur = next; next = next->next; } (*pphead) = NULL; printf("销毁成功!\n"); }
销毁时相同的,也要遍历整个链表将链表结点挨个释放,才不会造成内存泄漏的问题
到这里,链表的章节就结束了,本篇文章主要就是介绍了双向带头循环链表以及单向无头不循环链表的模拟实现,懂得这两种,其余形式的链表变形可谓是手到擒来。
最后,如果本篇文章对你有帮助的话,还请顺便点个免费的赞以及收藏~