前言:
在之前我们了解过了单链表的相关知识,我们知道单链表是不带头单向不循环链表,本章给大家介绍的就是双链表,双链表就是带头双向循环链表,当我们学习过单链表的知识,来实现双链表就比较容易了,接下来就让我们一起来了解一下吧!
1.双向链表的结构
如图所示:双向链表就是带头节点(哨兵位)+双向+循环链表
2.双向链表文件创建
这里的文件创建和单链表的文件创建相同,我们需要一个list.h来定义结构体和声明要用的函数,创建list.c来实现我们需要用的函数,test.c来测试我们的函数!
3.定义双向链表节点的结构
在这里我们需要定义:存放的数据 指向下一个节点的指针 指向前一个节点的指针
接下来就让我们来实现一下吧!
4.双向的链表的初始化
双向链表的初始化和单向链表的初始化不同,单项链表初始化以后是一个空链表,而双向链表初始化以后链表里只有一个头节点(哨兵位),里面没有任何数据!
//初始化
LNode* LBuynode(LDataType x)
{
LNode* node = (LNode*)malloc(sizeof(LNode));
if (node == NULL)
{
perror("malloc");
exit(1);
}
node->data = x;
node->next = node->prev = node;//前一个节点和后一个节点指向自己,保证循环
return node;
}
void LInit(LNode** pphead)
{
//先给链表创建一个哨兵位
*pphead = LBuynode(-1);
}
让我们来调试一下:
通过调试我们可以发现我们的形参改变了实参,并且节点自己指向自己!
5.双向链表的尾删和头删
注意:哨兵位节点不能被删除,节点的地址不能被修改!
所以在这里传参我们传一级指针即可,因为我们的形参不用改变实参
5.1双向链表的尾插
双向链表的尾插就是在链表的最后一个节点的后面插入一个新的节点,我们可以来画图分析一下!
接下来让我们实现一下代码
接下来让我们来打印一下:
5.2双向链表的头插
双链表的头插就是在哨兵位的后面插入节点,接下来我们画图分析一下!
//头插
void LPushFront(LNode* phead, LDataType x)
{
assert(phead);
LNode* newnode = LBuynode(x);
newnode->next = phead->next;
newnode->prev = phead;
newnode->next = phead->next;
phead->next = newnode;
}
6.双向链表的打印
双向链表的打印也很简单,我们需要遍历链表,但是要注意要从第一个有效节点开始遍历,因为哨兵位不存放有效数据,我们可以写一个while循环,如果节点走到哨兵位就结束!这样就可以遍历到所有的有效节点了!
//打印链表
void LPrint(LNode* phead)
{
LNode* perc = phead->next;
while (perc != phead)
{
printf("%d->", perc->data);
perc = perc->next;
}
printf("\n");
}
7.双向链表的尾删和头删
7.1双向链表的尾删
双向链表的尾删就是把最后一个节点释放掉
在这里我们可以把要删除的节点定义为del,这样有利于我们理解代码,也可以更好的释放del,可以避免找不到删除节点的情况
注意:这里断言链表必须有效,而且要有可以删除的数据
//尾删
void LPopBack(LNode* phead)
{
assert(phead && phead->next != phead);//链表必须有效,链表不能为空
LNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
代码测试:
7.2双向链表的头删
双向链表的头删就是将哨兵位下一个节点删除掉,这里的断言判断和尾删相同都要确保有数据可以删除!
//头删
void LPopFront(LNode* phead)
{
assert(phead && phead->next != phead);
LNode* del = phead->next;
phead->next = del->next;
del->next->prev = phead;
free(del);
del = NULL;
}
测试代码:
8.双链表的查找
双链表的查找其实和双链表的打印相似,都需要我们去遍历链表,如果遍历到就说明找到了,遍历完成就说明还是没有找到,while循环的条件和双链表的打印相同,接下来让我们来实现一下!
//查找
LNode* LFind(LNode* phead, LDataType x)
{
LNode* pucr = phead->next;
while (pucr != phead)
{
if (pucr->data == x)
{
//扎到了
return pucr;
}
pucr = pucr->next;
}
return NULL;
//没有找到
}
9.双链表在指定位置之后删除
在指定位置之后插入数据,也就是说需要创建一个新节点,再把他们连接起来,接下来让我们画图分析一下!
//在指定位置之后的插入
void LInset(LNode* pos, LDataType x)
{
assert(pos);
LNode* newnode = LBuynode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next = newnode;
newnode->next->prev = newnode;
//pos->next->prev = newnode;
//pos->next = newnode;
}
上面的两种写法都可以用,当然也可以把pos->next重新命名,办法有很多,哪个容易理解使用哪个即可!
10.双链表在指定位置删除
在我们学习了单链表的相关知识上上述的双链表知识,相信这个对大家来说就非常容易了!
//在指定位置删除
void LErase(LNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next = pos->prev;
free(pos);
pos = NULL;
}
11.双链表的销毁
双链表的销毁其实和我们的之前的单链表销毁相似,只需要保存销毁的下一个节点再销毁节点即可!我们可以再来画图分析一下
//链表的销毁
void LDestroy(LNode* phead)
{
assert(phead);
LNode* pcur = phead->next;
while (pcur != phead)
{
LNode* next = pcur->next;
free(pcur);
pcur = next;
}
//跳出循环时phead还没有销毁
free(phead);
phead = NULL;
}
注意:我们这里传的是一级指针,所以说形参并不会改变实参,所以需要我们手动再test.c中将plist设置为NULL!
12.细节问题
在我们写的代码中,我们可以发现在指定位置删除和链表销毁应该传2级指针,但是我们为了保持接口的一致性,采用一级指针
传一级指针的问题是:当形参phead=NULL时,实参plist并不会被改变,所以我们就需要手动将plist设置为NULL
在这里我们也可以修改一下我们的双链表初始化,我们可以返回值的形式返回:
这两种方式都可以,大家自行选择!
13.完整码源
List.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义双向链表节点的结构
typedef int LDataType;
typedef struct ListNode
{
LDataType data;
struct ListNode* next;
struct ListNode* prev;
}LNode;
//声明要使用的函数
//初始化
//void LInit(LNode** pphead);
LNode* LInit();
//打印链表
void LPrint(LNode* phead);
//尾插
void LPushBack(LNode* phead, LDataType x);
//头插
void LPushFront(LNode* phead, LDataType x);
//尾删
void LPopBack(LNode* phead);
//头删
void LPopFront(LNode* phead);
//在指定位置之后的插入
void LInset(LNode* pos, LDataType x);
//查找
LNode* LFind(LNode* phead, LDataType x);
//在指定位置删除
void LErase(LNode* pos);
//链表的销毁
void LDestroy(LNode* phead);
List.c
#include"List.h"
//初始化
LNode* LBuynode(LDataType x)
{
LNode* node = (LNode*)malloc(sizeof(LNode));
if (node == NULL)
{
perror("malloc");
exit(1);
}
node->data = x;
node->next = node->prev = node;//前一个节点和后一个节点指向自己,保证循环
return node;
}
//void LInit(LNode** pphead)
//{
// 先给链表创建一个哨兵位
// *pphead = LBuynode(-1);
//}
LNode* LInit()
{
LNode* phead = LBuynode(-1);
return phead;
}
//尾插
void LPushBack(LNode* phead, LDataType x)
{
assert(phead);
LNode* newnode = LBuynode(x);
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
//打印链表
void LPrint(LNode* phead)
{
LNode* perc = phead->next;
while (perc != phead)
{
printf("%d->", perc->data);
perc = perc->next;
}
printf("\n");
}
//头插
void LPushFront(LNode* phead, LDataType x)
{
assert(phead);
LNode* newnode = LBuynode(x);
newnode->next = phead->next;
newnode->prev = phead;
newnode->next = phead->next;
phead->next = newnode;
}
//尾删
void LPopBack(LNode* phead)
{
assert(phead && phead->next != phead);//链表必须有效,链表不能为空
LNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
//头删
void LPopFront(LNode* phead)
{
assert(phead && phead->next != phead);
LNode* del = phead->next;
phead->next = del->next;
del->next->prev = phead;
free(del);
del = NULL;
}
//查找
LNode* LFind(LNode* phead, LDataType x)
{
LNode* pucr = phead->next;
while (pucr != phead)
{
if (pucr->data == x)
{
//扎到了
return pucr;
}
pucr = pucr->next;
}
return NULL;
//没有找到
}
//在指定位置之后的插入
void LInset(LNode* pos, LDataType x)
{
assert(pos);
LNode* newnode = LBuynode(x);
newnode->next = pos->next;
newnode->prev = pos;
//pos->next = newnode;
//newnode->next->prev = newnode;
pos->next->prev = newnode;
pos->next = newnode;
}
//在指定位置删除
void LErase(LNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next = pos->prev;
free(pos);
pos = NULL;
}
//链表的销毁
void LDestroy(LNode* phead)
{
assert(phead);
LNode* pcur = phead->next;
while (pcur != phead)
{
LNode* next = pcur->next;
free(pcur);
pcur = next;
}
//跳出循环时phead还没有销毁
free(phead);
phead = NULL;
}
test.c
#include"List.h"
test1()
{
/*LNode* plist = NULL;
LInit(&plist);*/
LNode* plist = LInit();
LPushBack(plist, 1);
LPushBack(plist, 2);
LPushBack(plist, 3);
LPrint(plist);
/*LPushFront(plist, 2);
LPrint(plist);
LPushFront(plist, 1);
LPrint(plist);*/
//LPopBack(plist);
/*LPopFront(plist);
LPrint(plist);*/
/*LNode* find = LFind(plist, 2);
if (find == NULL)
{
printf("没有找到\n");
}
else
{
printf("找到了\n");
}*/
//LNode* find = LFind(plist, 1);
//LInset(find, 9);
//LErase(find);
//find = NULL;
//LPrint(plist);
/*LDestroy(plist);
plist = NULL;*/
}
int main()
{
test1();
return 0;
}
以上就是完整代码!
结语:
本章主要介绍了双向链表的增删查改功能的实现,希望对大家学习链表有帮助,在前几章也有会单链表的实现已经经典的约瑟夫环问题,有兴趣的友友可以支持一下,大佬互三!!!