前言
我们前面学习了单链表(点击这里跳转到单链表博客),那么应该发现了一个问题,就是我每次尾插和尾删都需要先把链表遍历一遍,这样是不是过于麻烦了,这时候我们就可以使用双向链表。
1. 链表的分类
- 带头和不带头
首先带头就是在链表的头节点前面再添加一个节点 ,这个节点的next指针指向的是头节点并且不存储任何的数据,就像一个哨兵一样(所以我们也把这个节点成为哨兵位)。
不带头就是我的链表的第一个节点就会存储数据。
- 单向和双向
单向就是我只有一个指针变量,只用来存放我下一个节点的地址。
双向就是我在单向的基础上,再加一个指针变量,这个指针变量是用来存放前一个节点的地址。
- 循环和不循环
是否循环是看你最后一个节点的next的指向;如果指向NULL
,就为不循环;如果指向的是我的头节点或者链表的其他节点,就为循环链表。
一个链表都是包含这三个元素的,这样下来,前前后后能组成八种链表,其中单链表和双链表是两个极端。
单链表的全名是不带头单向不循环链表 。
双链表的全名是带头双向循环链表 。
2. 双链表的结构
c
typedef int LTDATATYPE;
typedef struct ListNode
{
LTDATATYPE data;
struct ListNode* next; //指向下一个节点
struct ListNode* prev; //指向前一个节点 prev(previous的缩写)
}LTNode;
3. 双链表接口的实现
头文件(List.h
)
c
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDATATYPE;
typedef struct ListNode
{
LTDATATYPE data;
struct ListNode* next; //指向下一个
struct ListNode* prev; //指向前一个 prev(previous的缩写)
}LTNode;
//初始化
LTNode* LTInit();
void LTDesTroy(LTNode* phead);
void LTPrint(LTNode* phead);
//插入数据之前,链表必须初始化到只有一个头结点的情况
//不改变哨兵位的地址,因此传一级即可
//尾插
void LTPushBack(LTNode* phead, LTDATATYPE x);
//头插
void LTPushFront(LTNode* phead, LTDATATYPE x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDATATYPE x);
//删除pos节点
void LTErase(LTNode* pos);
LTNode* LTFind(LTNode* phead, LTDATATYPE x);
3.1 创建新节点
c
LTNode* BuyNode(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;
}
由于这段函数只用来服务该文件内的函数,所以不需要再头节点声明;如果后面需要再其他文件内使用,则再声明到头文件即可。
3.2 双链表的初始化
由于我们这个双链表是带头的,所以我们可以使用函数(创建新节点)来解决
c
//初始化
LTNode* LTInit()
{
LTNode* phead = BuyNode(-1);//这里可以传任意的值,我们要使用双链表的时候并不会使用哨兵位的数据
return phead;
}
创建哨兵位节点时,我们可以给随机的值,我们在使用双链表的时候并不会使用哨兵位的数据。
3.3 尾插
由于我们并不改变哨兵位的地址,因此传一级即可!!!
c
void LTPushBack(LTNode* phead, LTDATATYPE x)
{
assert(phead);
LTNode* NewNode = BuyNode(x);
//用来存放当前链表的尾节点
LTNode* pcur = phead->prev;
//改变新节点的指向
NewNode->prev = pcur;
NewNode->next = phead;
pcur->next = NewNode;
phead->prev = NewNode;
}
具体如下图
3.4 头插
c
//头插
void LTPushFront(LTNode* phead, LTDATATYPE x)
{
assert(phead);
//头插是在插入在哨兵位的后面,成为哨兵位后的第一个节点
//不是插入到哨兵位的前面
LTNode* NewNode = BuyNode(x);
LTNode* ptmp = phead->next;
NewNode->prev = phead;
NewNode->next = ptmp;
ptmp->prev = NewNode;
phead->next = NewNode;
}
原理和尾插大致相同,但要注意的是头插是在插入在哨兵位的后面,成为哨兵位后的第一个节点,而不是在链表的前面插入节点。
具体如下图:
3.5 尾删
我们除了判断phead是否为空以外,我们还要判断phead是否只有哨兵位。
c
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead && phead->prev != phead);
LTNode* del = phead->prev;
phead->prev = del->prev;
del->prev->next = phead;
free(del);
del = NULL;
}
我们先创建一个del来储存链表的尾节点,将del的前节点的next指针指向phead,再将phead的prev的指针指向del的前一个节点。
3.6 头删
c
//头删
void LTPopFront(LTNode* phead)
{
assert(phead && phead->prev != phead);
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
逻辑如下图
3.7 查找节点
根据节点的数据进行查找,如果找到了,返回被找到的节点;如果找不到,就返回NULL
c
LTNode* LTFind(LTNode* phead, LTDATATYPE x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
3.8 在指定节点后插入数据
c
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDATATYPE x)
{
assert(pos);
LTNode* NewNode = BuyNode(x);
NewNode->prev = pos;
NewNode->next = pos->next;
pos->next->prev = NewNode;
pos->next = NewNode;
}
注意:改变原链表的顺序不能变
假设我是先改变了pos的next指针,再改变原pos的下一个节点的指向就会很麻烦
所以我们先改变pos的下一个节点的prev,再改变pos的next
3.9 删除指定节点
c
//删除pos节点
void LTErase(LTNode* pos)
{
assert(pos && pos->next != pos);
LTNode* del = pos;
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(del);
del = NULL;
}
大致和头删一样,先改变前一个节点的指向和先改变后一个节点的指向都可以。
3.10 打印双链表
c
void LTPrint(LTNode* phead)
{
assert(phead != NULL);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d ->", pcur->data);
pcur = pcur->next;
}
}
3.11 销毁双链表
c
void LTDesTroy(LTNode* phead)
{
assert(phead);
LTNode* del = phead->next;
while (del != phead)
{
LTNode* pcur = del->next;
free(del);
del = pcur;
}
//跳出循环后,phead只剩下哨兵位
free(phead);
phead = NULL;
}
大体和单链表的销毁类似,就是要在循环结束后把哨兵位给释放掉。
4. 完整代码
头文件
c
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDATATYPE;
typedef struct ListNode
{
LTDATATYPE data;
struct ListNode* next; //指向下一个
struct ListNode* prev; //指向前一个 prev(previous的缩写)
}LTNode;
//初始化
LTNode* LTInit();
//销毁
void LTDesTroy(LTNode* phead);
//打印
void LTPrint(LTNode* phead);
//插入数据之前,链表必须初始化到只有一个头结点的情况
//不改变哨兵位的地址,因此传一级即可
//尾插
void LTPushBack(LTNode* phead, LTDATATYPE x);
//头插
void LTPushFront(LTNode* phead, LTDATATYPE x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDATATYPE x);
//删除pos节点
void LTErase(LTNode* pos);
LTNode* LTFind(LTNode* phead, LTDATATYPE x);
源文件
c
#include"List.h"
//创建新节点
LTNode* BuyNode(LTDATATYPE x)
{
LTNode* Node = (LTNode*)malloc(sizeof(LTNode));
Node->data = x;
Node->next = Node->prev = Node;
return Node;
}
//初始化
//void LTInit(LTNode** pphead);
LTNode* LTInit()
{
LTNode* phead = BuyNode(-1);
return phead;
}
//销毁
void LTDesTroy(LTNode* phead)
{
assert(phead);
LTNode* del = phead->next;
while (del != phead)
{
LTNode* pcur = del->next;
free(del);
del = pcur;
}
free(phead);
phead = NULL;
}
//打印
void LTPrint(LTNode* phead)
{
assert(phead != NULL);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d ->", pcur->data);
pcur = pcur->next;
}
}
//尾插
void LTPushBack(LTNode* phead, LTDATATYPE x)
{
assert(phead);
LTNode* NewNode = BuyNode(x);
LTNode* pcur = phead->prev;
NewNode->prev = pcur;
NewNode->next = phead;
pcur->next = NewNode;
phead->prev = NewNode;
}
//头插
void LTPushFront(LTNode* phead, LTDATATYPE x)
{
assert(phead);
//头插是在插入在哨兵位的后面,成为哨兵位后的第一个节点
//不是插入到哨兵位的前面
LTNode* NewNode = BuyNode(x);
LTNode* ptmp = phead->next;
NewNode->prev = phead;
NewNode->next = ptmp;
ptmp->prev = NewNode;
phead->next = NewNode;
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead && phead->prev != phead);
LTNode* del = phead->prev;
phead->prev = del->prev;
del->prev->next = phead;
free(del);
del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead && phead->prev != phead);
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDATATYPE x)
{
assert(pos);
LTNode* NewNode = BuyNode(x);
NewNode->prev = pos;
NewNode->next = pos->next;
pos->next->prev = NewNode;
pos->next = NewNode;
}
//删除pos节点
void LTErase(LTNode* pos)
{
assert(pos && pos->next != pos);
LTNode* del = pos;
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(del);
del = NULL;
}
LTNode* LTFind(LTNode* phead, LTDATATYPE x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
结语
最后感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言,也可以前往我的主页看更多好文哦(点击此处跳转到主页)。
如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!!!