【初阶数据结构与算法】第五篇:双链表
⭐️本篇博客我要给大家分享一下算法中的双链表。希望对大家有所帮助。
⭐️ 博主码云gitee链接:码云主页
目录
前言
🌏二、代码实现
🍯1.双链表定义
🍯2.双链表初始化两种形式
🍯3.双链表申请空间
🍯4.双链表尾插
🍯5.双链表尾删
🍯6.双链表打印
🍯7.双链表头插
🍯8.双链表头删
🍯9.双链表查找
🍯10.中间插入及头插和尾插优化
🍯11.中间删除及头删和尾删优化
🍯12.双链表销毁
🌏总结
前言
🌏一、双链表概念
🍤第一个结点属于多开辟的空间,为了方便头插头删,后续结点为真正的链表内容。
内容:
🍤一个指向前面结点的指针
🍤一个存储数据的空间
🍤一个指向后面结点的指针
🌏二、代码实现
🍯1.双链表定义
🍤与链表不同的是,双链表结构更复杂,但其实实现起来比单链表简单,并且还有一个哨兵
//此双链表带哨兵位typedef int typedefData;typedef struct Double_linked_list { typedefData Data; //数据 struct Double_linked_list* next;//下一个指针 struct Double_linked_list* prev;//上一个指针}Double_linked_list;
🍯2.双链表初始化两种形式
🍤初始化时,我们要将哨兵位地址传过去,因为我们要修改哨兵位的值,将哨兵位值修改为0,两个指针都指向他自己
//初始化不带返回值void InitializeNotReturn_Double_linked_list(Double_linked_list** pphead) { assert(pphead); *pphead = Application_Double_linked_list(0); (*pphead)->next = *pphead; (*pphead)->prev = *pphead;}
🍤这里不用传送哨兵位的值,因为直接在,函数中创建一个哨兵,并且将值设为0,两个指针指向他自己,最后将这个地址返回
//初始化带返回值Double_linked_list* InitializeReturn_Double_linked_list(void) { Double_linked_list* phead = Application_Double_linked_list(0); phead->next = phead; phead->prev = phead; return phead;}
🍯3.双链表申请空间
🍤新的空间设为我们需要的值,将两个指针都置为空,返回新空间的地址
//申请一个空间Double_linked_list* Application_Double_linked_list(typedefData x) { Double_linked_list* newnode = (Double_linked_list*)malloc(sizeof(Double_linked_list)); if (newnode == NULL) { printf("fail malloc:%s\n", strerror(errno)); exit(-1); } newnode->next = NULL; newnode->prev = NULL; newnode->Data = x; return newnode;}
🍯4.双链表尾插
🍤1.创建新结点存储数据
🍤2.原尾结点与新结点进行相互链接
🍤3.新结点与头结点相互链接
//尾插void PushBack_Double_linked_list(Double_linked_list* phead, typedefData x) { assert(phead); Double_linked_list* newnode = Application_Double_linked_list(x); Double_linked_list* tail = phead->prev; tail->next = newnode; newnode->prev = tail; phead->prev = newnode; newnode->next = phead;}
🍯5.双链表尾删
🍤1.找到尾结点和倒数第二个结点
🍤2.释放尾结点
🍤3.头结点和倒数第二个结点链接
void PopBack_Double_linked_list(Double_linked_list* phead) { assert(phead); Double_linked_list* tail = phead->prev; Double_linked_list* tailprev = tail->prev; phead->prev = tailprev; tailprev->next = phead; free(tail); tail = NULL;}
🍯6.双链表打印
🍤用一个cur节点指针来走,走到head的位置就停下来
void Print_Double_linked_list(Double_linked_list* phead) { assert(phead); Double_linked_list* cur = phead->next; printf("Head "); while (cur != phead) { printf(" %d ", cur->Data); cur = cur->next; } printf(" Head\n");}
🍯7.双链表头插
🍤1.创建新结点存储数据
🍤2.新结点与头结点后的第一个结点链接
🍤3.第三步,新结点与头结点链接
void PushFront_Double_linked_list(Double_linked_list* phead, typedefData x) { assert(phead); Double_linked_list* newnode = Application_Double_linked_list(x); Double_linked_list* cur = phead->next; Double_linked_list* curnext = cur->next; cur->next = newnode; newnode->prev = cur; curnext->prev = newnode; newnode->next = curnext;}
🍯8.双链表头删
🍤1.头结点与头删结点后的结点链接
🍤2.释放头删结点
void PopFront_Double_linked_list(Double_linked_list* phead) { assert(phead); Double_linked_list* cur = phead->next; Double_linked_list* curnext = cur->next; phead->next = curnext; curnext->prev = phead; free(cur); cur = NULL;}
🍯9.双链表查找
🍤直接遍历
Double_linked_list* Find_Double_linked_list(Double_linked_list* phead, typedefData x) { assert(phead); Double_linked_list* cur = phead->next; while (cur != phead) { if (cur->Data == x) { return cur; } cur = cur->next; } return NULL;}
🍯10.中间插入及头插和尾插优化
🍍中间插入
🍤任意位置前插入是在给定的
pos
结点之后插入
void Insert_Double_linked_list(Double_linked_list* pos, typedefData x) { assert(pos); Double_linked_list* newnode = Application_Double_linked_list(x); Double_linked_list* next = pos->next; newnode->next = next; next->prev = newnode; pos->next = newnode; newnode->prev = pos;}
🍍头插优化
🍤直接传头的下一个节点
void PushFront_Double_linked_list_other(Double_linked_list* pos, typedefData x) { assert(pos); //直接下一个节点插入就好了 Insert_Double_linked_list(pos->next, x);}
🍍尾插优化
🍤直接传头的上一个节点
void PushBack_Double_linked_list_other(Double_linked_list* pos, typedefData x) { assert(pos); //直接在哨兵位之前插入即可,循环链表 Insert_Double_linked_list(pos->prev, x);}
🍯11.中间删除及头删和尾删优化
🍍中间删除
🍤判断是不是只有头了,只有头则不要删除
void Erase_Double_linked_list(Double_linked_list* phead, Double_linked_list* pos) { assert(phead && pos); //防止头节点为空 assert(phead->next != phead); Double_linked_list* pospre = pos->prev; Double_linked_list* posnext = pos->next; pospre->next = posnext; posnext->prev = pospre; free(pos); pos = NULL;}
🍍头删优化
🍤同样判断是不是头,如果是头则不要删除
void PopFront_Double_linked_list_other(Double_linked_list* phead) { assert(phead); //防止头节点为空 assert(phead->next != phead); Erase_Double_linked_list(phead, phead->next);}
🍍尾删优化
🍤同样判断是不是头,如果是头则不要删除
void PopBack_Double_linked_list_other(Double_linked_list* phead) { assert(phead); //防止头节点为空 assert(phead->next != phead); Erase_Double_linked_list(phead, phead->prev);}
🍯12.双链表销毁
🍤轮流释放节点,最后释放头
void Destroy_Double_linked_list(Double_linked_list* phead) { assert(phead); Double_linked_list* cur = phead->next; Double_linked_list* curnext = cur->next; while (cur != phead) { free(cur); cur = curnext; curnext = curnext->next; } free(phead); phead = NULL;}
🌏总结
链表和顺序表的对比
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间上 | 物理上连续 | 逻辑上连续 |
随机访问 | 支持 | 不支持 |
任意位置插入删除 | 要移动元素,O(N) | 只要改变指针指向 |
插入数据 | 要考虑扩容,会带来一定的空间消耗 | 没有容量这个概念,可以按需申请和释放 |
缓存利用率 | 高 | 低 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |