一.双向链表的基本概念
双向链表(Doubly Linked List)是一种链式数据结构,每个节点包含三个部分:数据域(存储数据)、前驱指针(指向前一个节点)和后继指针(指向后一个节点)。与单向链表相比,双向链表支持双向遍历。
二.双向链表的结构
每个节点的结构如下:
- 数据域(data):
- 存储节点的实际值,可以是任意数据类型(如整数、字符串、对象等)
- 前驱指针(prev):
- 指向当前节点的前一个节点,链表头节点的prev指针通常设置为null,表示没有前驱节点
- 后继指针(next):
- 指向当前节点的后一个节点,链表尾节点的next指针通常设置为null,表示没有后继节点
三.双向链表的特点
- 双向遍历:可以从头节点向后遍历,也可以从尾节点向前遍历。
- 插入和删除高效:在已知节点位置时,插入或删除操作的时间复杂度为 O(1)。
- 空间开销较大:每个节点需要额外存储前驱指针,占用更多内存。
四.双向链表的代码实现:
双向链表实现的设计思想:
1.设计结构体:实现数据的存储,并通过指针链接节点;
prev指向前驱节点data存储数据next指向后继节点
2.创建节点:初始化节点,存放数据;
- 动态分配内存以创建新节点
- 初始化前后指针初始化为NULL
- 设置节点数据的值
3.插入数据(头插法)(尾插法):对创建节点,通过头节点查找链表来插入数据;
头插法
- 在链表头部插入新节点
- 处理头节点和新节点的指针关系
- 维护双向链接的完整性
尾插法
- 遍历找到链表末尾
- 在尾部插入新节点
- 维护前后节点的指针关系
4.删除节点(数据):根据输入数据,通过头节点查找存储该数据节点的位置;
- 处理前后节点的链接关系
- 释放被删除节点的内存
- 避免删除头节点
5.销毁链表
- 遍历整个链表
- 逐个释放节点内存
- 处理完毕后链表指针应置为NULL
5.显示数据:通过头节点访问链表;
- 从第一个有效节点开始遍历
- 打印每个节点的数据值
- 跳过头节点显示
头文件代码:
#ifndef DOUBLY_LIST_H
#define DOUBLY_LIST_H
#include <stdbool.h>
struct doubly_list //结构体设计
{
struct doubly_list* prev; //头指针
int data; //数据
struct doubly_list* next; //尾指针
};
//创建节点
struct doubly_list* Create_dlist(int data);
//头插法
bool Insert_dlis_head(struct doubly_list* head, int data);
//尾插法
bool Insert_dlist_tail(struct doubly_list* head, int data);
//删除数据
bool Delete_dlist(struct doubly_list* head);
//清除链表
void Destroy_dlist(struct doubly_list* head);
//显示链表
void Display(struct doubly_list* head);
#endif
实现代码部分展示:
1.设计结构体
实现原理:
prev 类型为struct doubly_list*,指向前驱节点的指针。
data 类型为int,存储节点中的整型数据,可根据需求替换为其他数据类型。
next 类型为struct doubly_list*,指向后继节点的指针。
代码:
struct doubly_list //结构体设计
{
struct doubly_list* prev; //头指针
int data; //数据
struct doubly_list* next; //尾指针
};
2.创建结构体
创建双向循环链表的节点,并返回该节点的指针。
实现原理:
-
内存分配
malloc动态分配一个doubly_list结构体大小的内存空间,并将指针赋给node。 -
初始化指针
node->prev = node和node->next = node将节点的prev和next指针都指向自身,形成一个自循环结构。 -
数据赋值
node->data = data将传入的data值赋给新节点的data字段。 -
返回节点指针
return node返回新创建的节点指针。
代码:
//创建双链表的节点
struct doubly_list* Create_dlist(int data)
{
struct doubly_list* node = malloc(sizeof(struct doubly_list));
node->prev = node;
node->data = data;
node->next = node;
return node; //要有返回值
}
3.插入数据(头插法)(尾插法)
头插法
实现原理:
-
创建一个新节点
node,并调用Create_dlist函数初始化节点数据。 -
将新节点的
next指针指向原头节点的下一个节点(head->next)。 -
将新节点的
prev指针指向头节点(head)。 -
更新原头节点下一个节点的
prev指针指向新节点(head->next->prev = node)。 -
更新头节点的
next指针指向新节点(head->next = node)。 -
返回
true表示插入成功。
图示:

代码:
//头插法
bool Insert_dlis_head(struct doubly_list* head, int data)
{
struct doubly_list* node = Create_dlist(data); //节点创建
node->next = head->next;
node->prev = head;
head->next->prev = node;
head->next = node;
return true;
}
尾插法
实现原理:
- 将头指针移动到链表的尾部(
head = head->prev)。 - 调用头插法函数
Insert_dlis_head(head, data)在尾部插入新节点。 - 返回
true表示插入成功。
代码:
//尾插法
bool Insert_dlist_tail(struct doubly_list* head, int data)
{
head = head->prev; //头指针移动到链表的尾部
Insert_dlis_head(head, data);
return true;
}
4.删除数据
实现双向链表中节点的删除操作。调整相邻节点的指针关系,确保链表结构正确性,并释放目标节点的内存。
实现原理:
node->prev->next = node->next;
1.将前驱节点的next指针指向当前节点的后继节点,跳过当前节点。
node->next->prev = node->prev;
2.将后继节点的prev指针指向当前节点的前驱节点,完成双向跳过。
node->next = node->prev = node;
3.将待删除节点的指针指向自身,防止悬空指针导致的未定义行为。
free(node);
4.释放节点内存,完成删除操作。
代码:
//删除节点
bool Delete_dlist(struct doubly_list* node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
node->next = node->prev = node;//防止悬空指针误操作
free(node);
return true;
}
5.销毁数据
接收一个指向双向链表头节点的指针head作为参数来实现双向循环链表的销毁操作。
实现原理:
- 初始化指针
p指向head->next(即第一个有效节点)。 - 循环条件
p != head遍历整个循环链表。 - 每次循环调用
Delete_dlist删除当前节点,并将p重新指向新的head->next节点。 - 循环结束后释放头节点
head的内存。
- 循环中每次删除后都从
head->next重新开始遍历,避免指针悬空问题。 - 通过
head->next == head判断单节点情况。 - 当传入的
head指针为NULL时,直接返回避免空指针解引用。 - 当链表仅有一个节点时(即
head->next == head),直接释放该节点内存并返回。
代码:
//销毁链表
void Destroy_dlist(struct doubly_list* head)
{
if(head == NULL)return;
if(head->next == head) //只有一个数据
{
free(head);
return;
}
struct doubly_list* p = head->next;
while(p!= head)
{
Delete_dlist(p);
p = head->next; //删除head后面的指针,删除后指针指向head
}
free(head);
}
5.显示数据
实现原理:
从head->next开始遍历,head作为哨兵节点不存储实际数据
使用p != head作为终止条件,确保完整遍历后回到头节点
每次循环打印当前节点数据p->data,然后移动到下一个节点p = p->next
代码:
//数据显示
void Display(struct doubly_list* head)
{
struct doubly_list* p = head->next;
while(p != head)
{
printf("%d ",p->data);
p = p->next;
}printf("\n");
}
完整代码:
list.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "doubly_list.h"
//创建双链表的节点
struct doubly_list* Create_dlist(int data)
{
struct doubly_list* node = malloc(sizeof(struct doubly_list));
node->prev = node;
node->data = data;
node->next = node;
return node; //要有返回值
}
//头插法
bool Insert_dlis_head(struct doubly_list* head, int data)
{
struct doubly_list* node = Create_dlist(data); //节点创建
node->next = head->next;
node->prev = head;
head->next->prev = node;
head->next = node;
return true;
}
//尾插法
bool Insert_dlist_tail(struct doubly_list* head, int data)
{
head = head->prev; //头指针移动到链表的尾部
Insert_dlis_head(head, data);
return true;
}
//删除节点
bool Delete_dlist(struct doubly_list* node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
node->next = node->prev = node;//防止悬空指针误操作
free(node);
return true;
}
//销毁链表
void Destroy_dlist(struct doubly_list* head)
{
if(head == NULL)return;
if(head->next == head) //只有一个数据
{
free(head);
return;
}
struct doubly_list* p = head->next;
while(p!= head)
{
Delete_dlist(p);
p = head->next; //删除head后面的指针,删除后指针指向head
}
free(head);
}
//数据显示
void Display(struct doubly_list* head)
{
struct doubly_list* p = head->next;
while(p != head)
{
printf("%d ",p->data);
p = p->next;
}printf("\n");
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "doubly_list.h"
int main()
{
struct doubly_list* head = Create_dlist(0);
for(int i = 0; i < 10; i++)
{
int data = random()%100;
//Insert_dlis_head(head,node);
Insert_dlis_head(head,data);
}
Display(head);
Destroy_dlist(head);
}
五.双向链表的应用场景
- 实现双向队列(Deque):支持从头部和尾部快速插入和删除。
- 浏览器历史记录:通过前进和后退按钮遍历访问记录。
- 撤销/重做功能:如文本编辑器中的操作记录。
六.双向链表与单向链表的对比
| 特性 | 双向链表 | 单向链表 |
|---|---|---|
| 指针数量 | 每个节点两个指针(prev/next) | 每个节点一个指针(next) |
| 遍历方向 | 支持双向遍历 | 仅支持单向遍历 |
| 空间占用 | 更高 | 更低 |
| 操作灵活性 | 更高(如反向删除) | 受限(需从头遍历) |
87

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



