(数据结构)双向链表
(数据结构)带头双向循环链表
前言
- 前面链表部分,咱们着重讲解了不带头单向不循环链表,简称单链表。那么链表其实也分很多种类适用于各种各样的场景。通过单链表的学习,其实我们已经大致了解了链表的绝大多数的内容,所以接下来我通过再为大家讲解一个带头双向循环链表,那么剩下的链表的种类大家就可以各自组合,各自书写了。
- 链表种类:
- 两种代表链表:
![]()
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。
整体的实现代码如下:(头文件)
#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; } }
- 到此,链表的内容就全部讲完了。