数据结构-双链表(Doubly Linked List )

一.双向链表的基本概念

        双向链表(Doubly Linked List)是一种链式数据结构,每个节点包含三个部分:数据域(存储数据)、前驱指针(指向前一个节点)和后继指针(指向后一个节点)。与单向链表相比,双向链表支持双向遍历。

二.双向链表的结构

        每个节点的结构如下:

  • 数据域(data)
    • 存储节点的实际值,可以是任意数据类型(如整数、字符串、对象等)
  • 前驱指针(prev)
    • 指向当前节点的前一个节点,链表头节点的prev指针通常设置为null,表示没有前驱节点
  • 后继指针(next)
    • 指向当前节点的后一个节点,链表尾节点的next指针通常设置为null,表示没有后继节点

三.双向链表的特点

  1. 双向遍历:可以从头节点向后遍历,也可以从尾节点向前遍历。
  2. 插入和删除高效:在已知节点位置时,插入或删除操作的时间复杂度为 O(1)。
  3. 空间开销较大:每个节点需要额外存储前驱指针,占用更多内存。

四.双向链表的代码实现:

双向链表实现的设计思想:

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.创建结构体

        创建双向循环链表的节点,并返回该节点的指针。

        实现原理:

  1. 内存分配
    malloc动态分配一个 doubly_list 结构体大小的内存空间,并将指针赋给 node

  2. 初始化指针
    node->prev = node 和 node->next = node 将节点的 prev 和 next 指针都指向自身,形成一个自循环结构。

  3. 数据赋值
    node->data = data 将传入的 data 值赋给新节点的 data 字段。

  4. 返回节点指针
    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作为参数来实现双向循环链表的销毁操作。

        实现原理:
  1. 初始化指针p指向head->next(即第一个有效节点)。
  2. 循环条件p != head遍历整个循环链表。
  3. 每次循环调用Delete_dlist删除当前节点,并将p重新指向新的head->next节点。
  4. 循环结束后释放头节点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);
}

五.双向链表的应用场景

  1. 实现双向队列(Deque):支持从头部和尾部快速插入和删除。
  2. 浏览器历史记录:通过前进和后退按钮遍历访问记录。
  3. 撤销/重做功能:如文本编辑器中的操作记录。

六.双向链表与单向链表的对比

特性双向链表单向链表
指针数量每个节点两个指针(prev/next)每个节点一个指针(next)
遍历方向支持双向遍历仅支持单向遍历
空间占用更高更低
操作灵活性更高(如反向删除)受限(需从头遍历)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值