双向链表专题
链表的分类:
链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:


带头:指的是链表中有哨兵位节点,该哨兵位节点即头节点
1. 双向链表的结构
注意:这⾥的“带头”跟前面我们说的“头节点”是两个概念,实际前面的在单链表阶段称呼不严
谨,但是为了同学们更好的理解就直接称为单链表的头节点。
带头链表面的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里“放哨
的”。
“哨兵位”存在的意义:
遍历循环链表避免死循环。
2. 双向链表的实现
1 typedef int LTDataType;
2 typedef struct ListNode
3 {
4 struct ListNode* next; //指针保存下⼀个节点的地址
5 struct ListNode* prev; //指针保存前⼀个节点的地址
6 LTDataType data;
7 }LTNode;
8 //void LTInit(LTNode** pphead);
9 LTNode* LTInit();
10 void LTDestroy(LTNode* phead);
11 void LTPrint(LTNode* phead);
12 bool LTEmpty(LTNode* phead);
13 void LTPushBack(LTNode* phead, LTDataType x);
14 void LTPopBack(LTNode* phead);
15 void LTPushFront(LTNode* phead, LTDataType x);
16 void LTPopFront(LTNode* phead);
17 //在pos位置之后插⼊数据
18 void LTInsert(LTNode* pos, LTDataType x);
19 void LTErase(LTNode* pos);
20 LTNode *LTFind(LTNode* phead,LTDataType x);
#define _CRT_SECURE_NO_WARNINGS
#include"list.h"
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}
//申请节点
LTNode* LTBuyNode(LTDataType x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL){perror("malloc fail");exit(1);}node->data = x;node->next = node->prev = node;return node;
}
//初始化
//void LTInit(LTNode** pphead)
//{
// //给双向链表创建一个哨兵位
// *pphead = LTBuyNode(-1);
//
//}
LTNode* LTInit()
{LTNode* phead = LTBuyNode(-1);return phead;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);//phead phead->prev newnodenewnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}
//尾删
void LTPopBack(LTNode* phead, LTDataType x)
{//链表必须有效且不能为空assert(phead && phead->next != phead);LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;//删除delfree(del);del = NULL;}
//头删
void LTPopFront(LTNode* phead, LTDataType x)
{assert(phead && phead->next != phead);LTNode* del = phead->next;phead->next = del->next;del->next->prev = phead;//删除delfree(del);del = NULL;
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}//没找到return NULL;
}
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
//删除pos节点
void LTErase(LTNode* pos)
{//pos理论上来说不能为phead,但是没有参数phead,无法增加校验assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}
//销毁链表
void LTDesTroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//此时pcur指向pheadfree(phead);phead = NULL;
}
3. 顺序表和双向链表的优缺点分析
不同点 | 顺序表 | 链表(单链表) |
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持O(1) | 不支持:O(n) |
任意位置插入或者删除元素 | 可能需要搬移元素 | 只需要修改指针指向 |
插入 | 动态顺序表 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和频繁删除 |