(数据结构)带头双向循环链表
前言
- 前面链表部分,咱们着重讲解了不带头单向不循环链表,简称单链表。那么链表其实也分很多种类适用于各种各样的场景。通过单链表的学习,其实我们已经大致了解了链表的绝大多数的内容,所以接下来我通过再为大家讲解一个带头双向循环链表,那么剩下的链表的种类大家就可以各自组合,各自书写了。
- 链表种类:
- 两种代表链表:
![]()
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。
整体的实现代码如下:(头文件)
#include<stdio.h>#include<stdlib.h>#include<assert.h>//对int类型取别名是为了后续方便更改数据类型typedef int LTDateType;//链表节点的结构typedef struct ListNode{struct ListNode *prev;struct ListNode *next;LTDateType date;}LTNode;//动态申请一个节点LTNode *BuyLTNode(LTDateType x);//对链表进行初始化LTNode *LTInit();//打印链表void LTPrint(LTNode* phead);//尾部插入数据void LTpushBack(LTNode *phead,LTDateType x);//尾部删除数据void LTpopBack(LTNode* phead);//头部插入数据void LTpushFront(LTNode *phead,LTDateType x);//头部删除数据void LTpopFront(LTNode* phead);//计算链表中有效数据的个数int LTSize(LTNode* phead);//查找数据值为x的节点LTNode *LTFind(LTNode *phead,LTDateType x);//在pos节点前插入数据为x的节点void LTInsert(LTNode* pos, LTDateType x);//删除pos位置处的节点void LTErase(LTNode* pos);//销毁链表void LTDestroy(LTNode* phead);
- 函数实现文件:
#include "List.h"LTNode *BuyLTNode(LTDateType x){LTNode *node= (LTNode*)malloc(sizeof (LTNode));if(node==NULL){perror("malloc fail");exit(-1);}node->date=x;node->next=NULL;node->prev=NULL;return node;}LTNode *LTInit(){LTNode *phead= BuyLTNode(-1);phead->prev=phead;phead->next=phead;return phead;}void LTPrint(LTNode* phead){assert(phead);printf("phead<->");LTNode *cur=phead->next;while(cur!=phead){printf("%d<->",cur->date);cur=cur->next;}printf("phead\n");}void LTpushBack(LTNode *phead,LTDateType x){//判断phead是否为空指针assert(phead);//尾节点如下,无需再去遍历寻找尾节点LTNode *tail=phead->prev;//创建一个新节点LTNode *newnode=BuyLTNode(x);//改变指针指向,将尾节点与新节点相连newnode->prev=tail;tail->next=newnode;newnode->next=phead;phead->prev=newnode;}void LTpopBack(LTNode* phead){assert(phead);if (phead->next == phead)exit(-1);else{LTNode *tail=phead->prev;phead->prev=tail->prev;phead->prev->next=phead;free(tail);}}void LTpushFront(LTNode *phead,LTDateType x){assert(phead);/* LTNode *newnode= BuyLTNode(x);newnode->next=phead->next;phead->next->prev=newnode;phead->next=newnode;newnode->prev=phead;*/LTNode *newnode= BuyLTNode(x);LTNode *first=phead->next;phead->next=newnode;newnode->prev=phead;newnode->next=first;first->prev=newnode;}void LTpopFront(LTNode* phead){assert(phead);if (phead->next == phead)exit(-1);else{/* LTNode *first=phead->next;phead->next=first->next;phead->next->prev=phead;free(first);*/LTNode *first=phead->next;LTNode *second=first->next;free(first);phead->next=second;second->prev=phead;}}int LTSize(LTNode* phead){assert(phead);int size=0;LTNode *cur=phead->next;while(cur!=phead){size++;cur=cur->next;}return size;}LTNode *LTFind(LTNode *phead,LTDateType x){assert(phead);LTNode *cur=phead->next;while(cur!=phead){if(cur->date==x)return cur;cur=cur->next;}return NULL;}void LTInsert(LTNode* pos, LTDateType x){assert(pos);LTNode *posPrev=pos->prev;LTNode *newnode= BuyLTNode(x);posPrev->next=newnode;newnode->prev=posPrev;newnode->next=pos;pos->prev=newnode;}void LTErase(LTNode* pos){assert(pos);/* pos->prev->next=pos->next;pos->next->prev=pos->prev;free(pos);*/LTNode *posPrev=pos->prev;LTNode *posNext=pos->next;free(pos);posPrev->next=posNext;posNext->prev=posPrev;}void LTDestroy(LTNode* phead){assert(phead);LTNode *cur=phead->next;while(cur!=phead){LTNode *next=cur->next;free(cur);cur=next;}free(phead);}
- 测试文件:(这里的测试文件,其实就是大家调用写好的函数,看功能是否正确,大家可以自行更改,这里只是一个示例)
#include "List.h"void test1(){LTNode *plist=LTInit();LTpushBack(plist,1);LTpushBack(plist,2);LTpushBack(plist,3);LTpushBack(plist,4);LTPrint(plist);int c= LTSize(plist);printf("%d\n",c);LTNode *node= LTFind(plist,3);LTInsert(node,6);LTErase(node);LTPrint(plist);LTDestroy(plist);plist=NULL;}int main(){test1();return 0;}
函数逐个讲解:
- 第一个动态申请链表节点:
LTNode *BuyLTNode(LTDateType x){LTNode *node= (LTNode*)malloc(sizeof (LTNode));if(node==NULL){perror("malloc fail");exit(-1);}node->date=x;node->next=NULL;node->prev=NULL;return node;}
- 这个函数没什么好说的,就是用malloc和sizeof来申请节点,如果申请成功就赋值,失败则exit。
- 初始化函数:
LTNode *LTInit() {LTNode *phead= BuyLTNode(-1);phead->prev=phead;phead->next=phead;return phead; }
打印函数:
void LTPrint(LTNode* phead) {assert(phead);printf("phead<->");LTNode *cur=phead->next;while(cur!=phead){printf("%d<->",cur->date);cur=cur->next;}printf("phead\n"); }
4. 尾部插入函数:void LTpushBack(LTNode *phead,LTDateType x) {//判断phead是否为空指针assert(phead);//尾节点如下,无需再去遍历寻找尾节点LTNode *tail=phead->prev;//创建一个新节点LTNode *newnode=BuyLTNode(x);//改变指针指向,将尾节点与新节点相连newnode->prev=tail;tail->next=newnode;newnode->next=phead;phead->prev=newnode;}
5. 尾部删除函数void LTpopBack(LTNode* phead) {assert(phead);if (phead->next == phead)exit(-1);else{LTNode *tail=phead->prev;phead->prev=tail->prev;phead->prev->next=phead;free(tail);} }
6. 头部插入函数void LTpushFront(LTNode *phead,LTDateType x) {assert(phead); /* LTNode *newnode= BuyLTNode(x);newnode->next=phead->next;phead->next->prev=newnode;phead->next=newnode;newnode->prev=phead; */LTNode *newnode= BuyLTNode(x);LTNode *first=phead->next;phead->next=newnode;newnode->prev=phead;newnode->next=first;first->prev=newnode;}
7. 头部删除函数void LTpopFront(LTNode* phead) {assert(phead);if (phead->next == phead)exit(-1);else{/* LTNode *first=phead->next;phead->next=first->next;phead->next->prev=phead;free(first);*/LTNode *first=phead->next;LTNode *second=first->next;free(first);phead->next=second;second->prev=phead;}}
8. 计算有效节点个数int LTSize(LTNode* phead) {assert(phead);int size=0;LTNode *cur=phead->next;while(cur!=phead){size++;cur=cur->next;}return size; }
9. 查找数据的值为x的节点LTNode *LTFind(LTNode *phead,LTDateType x) {assert(phead);LTNode *cur=phead->next;while(cur!=phead){if(cur->date==x)return cur;cur=cur->next;}return NULL;}
- 这里这个函数功能和上一个函数,也就是计算有效个数函数和打印函数实现逻辑类似,就不多说了。
- 在pos位置前插入值为x的节点
void LTInsert(LTNode* pos, LTDateType x) {assert(pos);LTNode *posPrev=pos->prev;LTNode *newnode= BuyLTNode(x);posPrev->next=newnode;newnode->prev=posPrev;newnode->next=pos;pos->prev=newnode;}
- 这个函数也可以复用在尾插和头插函数里
- 删除pos位置的节点
void LTErase(LTNode* pos) {assert(pos);/* pos->prev->next=pos->next;pos->next->prev=pos->prev;free(pos);*/LTNode *posPrev=pos->prev;LTNode *posNext=pos->next;free(pos);posPrev->next=posNext;posNext->prev=posPrev;}
- 这个函数也可以复用在尾删和头删里
- 链表销毁函数
void LTDestroy(LTNode* phead) {assert(phead);LTNode *cur=phead->next;while(cur!=phead){LTNode *next=cur->next;free(cur);cur=next;}}
- 到此,链表的内容就全部讲完了。