链表-双向链表
- 1.链表的分类
-
- [1.1 分类依据](#1.1 分类依据)
- [1.2 常用类型](#1.2 常用类型)
- 2.双向链表的
-
- [2.1 双向链表的结构](#2.1 双向链表的结构)
- [2.2 双向链表的操作](#2.2 双向链表的操作)
-
- [2.2.1 **初始化**](#2.2.1 初始化)
- [2.2.2 **尾插**](#2.2.2 尾插)
- [2.2.3 **头插**](#2.2.3 头插)
- [2.2.4 **尾删**](#2.2.4 尾删)
- [2.2.5 **头删**](#2.2.5 头删)
- [2.2.6 在pos位置之后插入数据](#2.2.6 在pos位置之后插入数据)
- [2.2.7 删除pos节点](#2.2.7 删除pos节点)
- [2.2.8 查找](#2.2.8 查找)
- [2.2.9 销毁](#2.2.9 销毁)
1.链表的分类
1.1 分类依据
单向or双向
带头or不带头
(一般会称d1为头节点,但实际上head这个哨兵节点才是头节点)
循环or不循环
综上所述,222共有8种链表
1.2 常用类型
我们一般最常用的就是单链表和双链表。
单链表:不带头单向不循环链表
双向链表:带头双向循环链表
2.双向链表的
2.1 双向链表的结构
双向链表包含三个部分,:
1.数据
2.指向下一个节点的指针
3.指向上一个节点的指针
c
struct ListNode
{
int data;
struct ListNode* next;
struct ListNode* prev;
}
ps:单链表和双向链表的初始状态?
单链表为空链表
双向链表剩下一个头结点(即哨兵位)
2.2 双向链表的操作
2.2.1 初始化
方法一
c
void LTInit(LTNode** pphead);
c
void LTInit(LTNode** pphead)
{
//给双向链表创建一个哨兵位
*pphead = LTBuyNode(-1);//哨兵位不存储有效数据
}
方法二
c
LTNode* LTInit();
c
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
打印双向链表
c
void LTPrint(LTNode* phead);
c
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
c
void LTInit(LTNode** pphead)
{
//给双向链表创建一个哨兵位
*pphead = LTBuyNode(-1);//哨兵位不存储有效数据
}
2.2.2 尾插
c
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
c
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);//哨兵位空,那就不是一个有效的双向链表
LTNode* newnode = LTBuyNode(x);
//phead phead->prev newnode
newnode->prev = phead->prev;//新节点头指向原链表的尾节点
newnode->next = phead;//新节点尾节点指向哨兵位
phead->prev->next = newnode;//原本的尾节点phead->prev指向新的尾节点
phead->prev = newnode;//哨兵位指向新的尾节点
}
2.2.3 头插
c
void LTPushFront(LTNode* phead, LTDataType x);
c
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead newnode phead->next
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
2.2.4 尾删
c
void LTPopBack(LTNode* phead);
c
void LTPopBack(LTNode* phead)
{
//链表必须有效且链表不能为空(只有一个哨兵位)
assert(phead && phead->next != phead);
LTNode* del = phead->prev;
//phead del->prev del
del->prev->next = phead;
phead->prev = del->prev;
//删除del节点
free(del);
del = NULL;
}
2.2.5 头删
c
void LTPopFront(LTNode* phead);
c
void LTPopFront(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
//phead del del->next
phead->next = del->next;
del->next->prev = phead;
//删除del节点
free(del);
del = NULL;
}
2.2.6 在pos位置之后插入数据
c
void LTInsert(LTNode* pos, LTDataType x);
c
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
2.2.7 删除pos节点
c
void LTErase(LTNode* pos);
c
void LTErase(LTNode* pos)
{
//pos理论上来说不能为phead,但是没有参数phead,无法增加校验
assert(pos);
//pos->prev pos pos->next
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
PS:LTErase参数理论上要传二级,因为我们需要让形参的改变影响到实参,但是为了保持接口一致性才传的一级。
2.2.8 查找
c
LTNode* LTFind(LTNode* phead, LTDataType x);
c
LTNode* LTFind(LTNode* phead, LTDataType x)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
2.2.9 销毁
c
void LTDesTroy(LTNode* phead);
c
void LTDesTroy(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//此时pcur指向phead,而phead还没有被销毁
free(phead);
phead = NULL;
}
ps:LTDestroy参数理论上要传二级,因为我们需要让形参的改变影响到实参,但是为了保持接口一致性才传的一级。传一级存在的问题是,当形参phead置为NULL后,实参plist不会被修改为NULL,因此解决办法是:调用完方法后手动将实参置为NULL。