目录
前言:
前两篇文章我们介绍了单链表,这篇文章来介绍一下双链表(带头双向循环链表),再分享双链表的知识和代码实现之前,我们来简单介绍一下链表的分类,那么就让我们开启今天这篇文章吧!
一、链表的分类
链表的分类有:
带头与不带头、单向与双向、循环与不循环。
因此,链表的种类就有8种。

但是我们最常用的只有两种:不带头单向不循环链表和带头双向循环链表,也就是我们这两篇文章所说的单链表和双链表。
二、双向链表的概念与结构
概念:

双向链表就是带头双向循环链表,如图所示。
结构:
和单链表一样,物理结构:连续; 逻辑结构:不一定连续。
三、双向链表的代码实现
1.定义双向链表的结构
双向链表中有三个结构:data、*next、*prev
data :保存有效数据
struct ListNode* next :指针保存下⼀个结点的地址
struct ListNode* prev :指针保存前⼀个结点的地址
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
2.申请新节点
//申请新节点
ListNode* LTBuyNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = newnode;
newnode->prev = newnode;
return newnode;
}
3.初始化
初始化就是把头节点设置为存储无效数据,以便后面“放哨”用。
//初始化
ListNode* LTInit()
{
ListNode* phead = LTBuyNode(-1);
return phead;
}
4.打印链表
//打印链表
void LTPrint(ListNode* phead)
{
assert(phead);
ListNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
5.尾插
//尾插
void LTPushBack(ListNode* phead, LTDataType x)
{
ListNode* newnode = LTBuyNode(x);
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
6.头插
//头插
void LTPushFront(ListNode* phead, LTDataType x)
{
ListNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
7.判断链表是否为空(是否只有一个哨兵位节点)
//判断链表是否为空(是否只有一个哨兵位节点)
bool LTEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead;
}
8.尾删
删除操作之前需要判断链表内是否为空,否则无法进行删除。
//尾删
void LTDelBack(ListNode* phead)
{
assert(!LTEmpty(phead));
ListNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
9.头删
//头删
void LTDelFront(ListNode* phead)
{
assert(!LTEmpty(phead));
ListNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
10.查找值为pos的数据
//查找数据
ListNode* LTFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
11.在pos位置之后插入数据
//在pos之后插入节点
void LTInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
12.删除pos位置的数据
//删除pos节点
void LTErase(ListNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
13.销毁链表
//销毁链表
void LTDestory(ListNode* phead)
{
ListNode* pcur = phead->next;
while (pcur != phead)
{
ListNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
进行完销毁链表函数之后,虽然已经将链表内的数据都释放掉了,但还需要将链表的头节点置为NULL,防止后面成为野指针被使用。
四、完整代码分享
List.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
//初始化
ListNode* LTInit();
//申请新节点
ListNode* LTBuyNode(LTDataType x);
//打印链表
void LTPrint(ListNode* phead);
//判断链表是否为空(是否只有一个哨兵位节点)
bool LTEmpty(ListNode* phead);
//尾插
void LTPushBack(ListNode* phead, LTDataType x);
//头插
void LTPushFront(ListNode* phead, LTDataType x);
//尾删
void LTDelBack(ListNode* phead);
//头删
void LTDelFront(ListNode* phead);
//查找数据
ListNode* LTFind(ListNode* phead, LTDataType x);
//在pos之后插入节点
void LTInsert(ListNode* pos, LTDataType x);
//删除pos节点
void LTErase(ListNode* pos);
//销毁链表
void LTDestory(ListNode* phead);
List.c
#include"List.h"
//申请新节点
ListNode* LTBuyNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = newnode;
newnode->prev = newnode;
return newnode;
}
//初始化
ListNode* LTInit()
{
ListNode* phead = LTBuyNode(-1);
return phead;
}
//销毁链表
void LTDestory(ListNode* phead)
{
ListNode* pcur = phead->next;
while (pcur != phead)
{
ListNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
//打印链表
void LTPrint(ListNode* phead)
{
assert(phead);
ListNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//尾插
void LTPushBack(ListNode* phead, LTDataType x)
{
ListNode* newnode = LTBuyNode(x);
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
//头插
void LTPushFront(ListNode* phead, LTDataType x)
{
ListNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
//判断链表是否为空(是否只有一个哨兵位节点)
bool LTEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead;
}
//尾删
void LTDelBack(ListNode* phead)
{
assert(!LTEmpty(phead));
ListNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
//头删
void LTDelFront(ListNode* phead)
{
assert(!LTEmpty(phead));
ListNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
//查找数据
ListNode* LTFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在pos之后插入节点
void LTInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
//删除pos节点
void LTErase(ListNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
test.c
test,.c也就是测试函数,用来测试函数的功能。
#include"List.h"
void test1()
{
ListNode* plist=LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPrint(plist);
//LTPushFront(plist, 1);
//LTPrint(plist);
//LTPushFront(plist, 2);
//LTPrint(plist);
//LTPushFront(plist, 3);
//LTPrint(plist);
//LTPushFront(plist, 4);
//LTPrint(plist);
//LTDelBack(plist);
//LTPrint(plist);
//LTDelBack(plist);
//LTPrint(plist);
//LTDelBack(plist);
//LTPrint(plist);
//LTDelBack(plist);
//LTPrint(plist);
//LTDelBack(plist);
//LTPrint(plist);
//LTDelFront(plist);
//LTPrint(plist);
//LTDelFront(plist);
//LTPrint(plist);
//LTDelFront(plist);
//LTPrint(plist);
//LTDelFront(plist);
//LTPrint(plist);
//LTDelFront(plist);
//LTPrint(plist);
ListNode* pos = LTFind(plist, 3);
//if (pos != NULL)
//{
// printf("找到了");
//}
//LTInsert(pos, 100);
//LTPrint(plist);
//
//LTErase(pos);
//LTPrint(plist);
LTDestory(plist);
plist = NULL;
}
int main()
{
test1();
return 0;
}
五、博主手记
这一部分是将博主记的笔记和解题思路分享给大家,大家可以参考一下思路。



结语:
双向链表对于单链表来说确实使用起来更加的方便,但是考虑到占用空间的问题,大家还是不能盲目的使用,还应该具体情况具体分析,来使用合适的结构完成。
1135

被折叠的 条评论
为什么被折叠?



