双向链表的实现
目录
一、双向链表的概念及结构
1.1.双向链表的概念
带头双向循环链表
1.2.双向链表的结构
1.2.1.结构图

1.2.2.节点的组成
**数据域:**用于存储该节点的数据
**指针域:**用于存储上一个节点的地址(前驱指针)和下一个节点的地址(后置指针)
cpp
struct ListNode
{
int data;//节点数据
struct ListNode* next;//指向下一个节点的指针变量
struct ListNode* prev;//指向前一个节点的指针变量
};
二、双向链表的实现
2.1.双向链表文件结构
- 头文件(List.h):双向链表的结构创建,双向链表的方法声明
- 源文件(List.c):双向链表的方法实现
- 测试文件(test.c):测试数据结构的方法
2.2.头文件编写(List.h)
2.2.1.头文件包含
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
2.2.2.节点数据类型重定义
cpp
typedef int LTDataType;
2.2.3.双向链表节点定义
cpp
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
2.2.4.双向链表的初始化
法1:
cpp
void LTInit(LTNode** pphead);
法2:
cpp
LTNode* LTInit();
2.2.5.双向链表的尾插
cpp
void LTPushBack(LTNode* phead,LTDataType x);
**注:**哨兵位不会改变,所以传一级指针即可
2.2.6.双向链表的头插
cpp
void LTPushFront(LTNode* phead,LTDataType x);
2.2.7.双向链表的尾删
cpp
void LTPopBack(LTNode* phead);
2.2.8.双向链表的头删
cpp
void LTPopFront(LTNode* phead);
2.2.9.双向链表的查找
cpp
LTNode* LTFind(LTNode* phead, LTDataType x);
2.2.10.pos位置后插入数据
cpp
void LTInsert(LTNode* pos, LTDataType x);
2.2.11.删除pos节点
cpp
void LTErase(LTNode* pos);
2.2.12.双向链表的销毁
cpp
void LTDesTroy(LTNode* phead);
2.3.源文件编写(List.c)
2.3.1.头文件包含
cpp
#include "SList.h"
2.3.2.节点申请函数
cpp
LTNode* LTBuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail!");
exit(1);
}
node->data = x;
node->prev = node->next = node;
return node;
}
**注:**由于双向链表是循环链表,所以新节点的前驱指针和后置指针都先指向自己
2.3.3.双向链表的初始化
法1:
cpp
void LTInit(LTNode** pphead)
{
/*创建哨兵位*/
*pphead = LTBuyNode(-1);
}
法2:
cpp
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
2.3.4.双向链表的打印
cpp
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
**解析:**从头节点的下一个节点开始打印,当节点再次走到头节点时停止打印
2.3.5.双向链表的尾插
cpp
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
解析:
- 断言判断传参是否为NULL
- 创建临时变量newnode接收新节点地址
- 新节点的前驱指针指向上一个节点
- 新节点的后置指针指向哨兵位
- 上一个节点的后置指针指向新节点
- 哨兵位的前驱指针指向新节点

2.3.6.双向链表的头插
cpp
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
解析:
- 断言判断传参是否为NULL
- 创建临时变量newnode接收新节点地址
- 新节点的后置指针指向下一个节点
- 新节点的前驱指针指向哨兵位
- 下一个节点的前驱节点指向新节点
- 哨兵位的后置指针指向新节点

2.3.7.双向链表的尾删
cpp
void LTPopBack(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
解析:
- 断言判断传参是否为NULL,双向链表是否只有哨兵位
- 创建临时变量del,存放要删除的尾节点地址
- 尾节点前一个节点的后置指针指向哨兵位
- 哨兵位的前驱指针指向尾节点的前一个节点
- 释放尾节点,将del置为NULL

2.3.8.双向链表的头删
cpp
void LTPopFront(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
phead->next = del->next;
del->next->prev = phead;
free(del);
del = NULL;
}
解析:
- 断言判断传参是否为NULL,双向链表是否只有哨兵位
- 创建临时变量del,存放要删除的哨兵位下一个节点地址
- 哨兵位下下个节点的前驱指针指向哨兵位
- 哨兵位的后置指针指向哨兵位下下个节点
- 释放哨兵位下一个节点,将del置为NULL

2.3.9.双向链表的查找
cpp
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.3.10.pos位置后插入数据
cpp
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;
}
解析:
- 断言判断传参是否为NULL
- 创建临时变量newnode接收新节点地址
- 新节点的后置指针指向pos位下一个节点
- 新节点的前驱指针指向pos位的节点
- pos位下一个节点的前驱指针指向新节点
- pos位节点的后置指针指向新节点

2.3.11.删除pos节点
cpp
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
解析:
- 断言判断传参是否为NULL
- pos位下一个节点的前驱指针指向pos上一个节点
- pos位上一个节点的后置指针指向pos下一个节点
- 释放pos位节点,将pos置为NULL

2.3.12.双向链表的销毁
cpp
void LTDesTroy(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
解析:
- 断言判断传参是否为NULL
- 创建临时变量pcur存放哨兵位下一个节点
- 循环释放每一个节点
- 创建临时变量next存放pcur的下一个节点
- 释放完pcur节点后pcur走到下一个节点
- 最后释放哨兵位,将phead置为NULL
2.4.测试文件编写(test.c)
2.4.1.头文件包含
cpp
#include "SList.h"
2.4.2.测试文件01
cpp
void ListTest01()
{
/*哨兵位初始化 法1*/
LTNode* plist = NULL;
LTInit(&plist);
/*哨兵位初始化 法2*/
LTNode* plist = LTInit();
/*双向链表的尾插*/
LTPushBack(plist,1);
LTPushBack(plist,2);
LTPushBack(plist,3);
/*双向链表的头插*/
LTPushFront(plist,1);
LTPushFront(plist,2);
LTPushFront(plist,3);
/*双向链表的尾删*/
LTPopBack(plist);
/*双向链表的头删*/
LTPopFront(plist);
/*双向链表的查找*/
LTNode* find = LTFind(plist,3);
if(find == NULL)
{
printf("找不到!\n");
}
else
{
printf("找到了")
}
/*pos位后插入数据*/
LTInsert(find,66);
/*删除pos节点*/
LTErase(find);
/*双向链表的打印*/
LTPrint(plist);
/*双向链表的销毁*/
LTDesTroy(plist);
/*手动置空*/
plist = NULL;
}
int main()
{
ListTest01();
return 0;
}
三、总结
本篇博客是对于数据结构中双向链表实现的整理归纳,后续还会更新链表OJ试题等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~