数据结构 双向链表(2)--双向链表的实现

目录

1.双向链表的实现


1.双向链表的实现

实现双向链表和实现单链表的步骤一样,小编也是用三个文件编写完成的。分别是一个头文件

(.h) , 一个实现文件(.c)和一个测试文件(.c)。 下面对这三个文件的代码按照功能模块,函数逻辑

等进行详细解释,进一步帮助理解单链表的实现。

1.1 头文件(list.h)

  • List.h(头文件):定义双向链表的结构体、函数声明,包含必要的头文件(如stdio.h、stdlib.h

等),是整个双向链表实现的接口定义文件,规定了双向链表的操作集合。

cs 复制代码
#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;
}LTNode;


void LTPrint(LTNode* phead);
//双向链表的初始化
//void LTInit(LTNode** pphead);

LTNode* LTInit();
//传二级:违背了接口一致性
//void LTDesTroy(LTNode** pphead);
//传一级:调用完成之后将实参手动置为NULL(推荐)
void LTDesTroy(LTNode* phead);

//头结点要发生改变,传二级
//头结点不发生改变,传一级
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);

bool LTEmpty(LTNode* phead);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);

LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x);
//课下练习------在指定位置之前插入数据

//删除pos位置的结点
void LTErase(LTNode* pos);

1.2 实现文件(List.c)

  • List.c(实现文件):实现List.h中声明的所有函数,包括链表的初始化、插入、删除、查找、销

毁等操作,是双向链表功能的具体实现。

下面将这些函数的功能进行逐一介绍:

1. 节点创建函数-LTBuyNode 函数

cs 复制代码
#include"List.h"

LTNode* LTBuyNode(LTDataType x)
{
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    if (newnode == NULL)
    {
        perror("malloc fail!");
        exit(1);
    }
    newnode->data = x;
    newnode->next = newnode->prev = newnode;

    return newnode;
}

- 核心功能:创建并初始化一个双向链表节点。

  • 详细说明:

  • 首先调用 malloc 函数为新节点分配内存空间,空间大小为 struct ListNode 结构体的大小。

  • 检查内存分配是否成功:若 malloc 返回 NULL ,则通过 perror 打印错误信息"malloc

fail!",并调用 exit(1) 终止程序。

  • 初始化节点的数据域:将参数 x 赋值给新节点的 data 成员。

- 初始化节点的指针域:将新节点的 next 和 prev 指针都指向自身(这是为了方便后续插入

链表时的指针调整,尤其是在创建头节点或单独节点时)。

  • 联系:是其他插入类函数(如LTPushBack、LTPushFront、LTInsert等)的基础,这些函数

需要创建新节点时都会调用它。

- 时间复杂度:O(1),仅涉及固定的内存分配和初始化操作。

- 空间复杂度:O(1),只分配了一个节点的内存空间。

2. 链表打印函数-LTPrint 函数

cs 复制代码
void LTPrint(LTNode* phead)
{
    LTNode* pcur = phead->next;
    while (pcur != phead)
    {
        printf("%d -> ", pcur->data);
        pcur = pcur->next;
    }
    printf("\n");
}

- 核心功能:打印双向链表中除头节点外的所有有效节点数据。

  • 详细说明:

  • 定义临时指针 pcur ,并将其初始化为头节点 phead 的 next 指针(即第一个有效节点的

地址)。

  • 循环遍历链表:当 pcur 不等于头节点 phead 时,进入循环(因为双向链表为循环结构,

头节点的 next 指向第一个有效节点,最后一个有效节点的 next 指向头节点,以此作为遍

历结束条件)。

  • 打印当前节点数据:在循环中,打印 pcur->data 的值,并附加" -> "作为分隔符。

  • 移动指针:将 pcur 更新为 pcur->next ,继续遍历下一个节点。

  • 遍历结束后,打印一个换行符,使输出格式更清晰。

  • 联系:在插入、删除等操作后调用,用于查看链表当前的状态。

- 时间复杂度:O(n),n为链表中有效节点的个数,需要遍历所有有效节点。

- 空间复杂度:O(1),只使用了一个临时指针变量。

3. 链表初始化函数-LTInit 函数

cs 复制代码
LTNode* LTInit()
{
    LTNode* phead = LTBuyNode(-1);
    return phead;
}

- 核心功能:初始化双向链表,创建一个带哨兵位(头节点)的循环双向链表。

  • 详细说明:

- 调用 LTBuyNode 函数创建一个新节点,传入的参数为 -1 (该值无实际意义,仅作为头

节点的占位数据)。

- 由于 LTBuyNode 函数会将新节点的 next 和 prev 都指向自身,因此创建的头节点会

形成一个自循环的结构(即头节点的 next 和 prev 都指向自己),这是双向循环链表的初

始状态。

  • 返回创建的头节点指针,作为初始化后链表的入口。

  • 后续对链表的所有操作(如插入、删除等)都以该头节点为基准进行。

  • 联系:是使用双向链表的第一步,后续所有操作都基于该初始化后的链表。

- 时间复杂度:O(1),本质是调用LTBuyNode创建头节点,操作固定。

- 空间复杂度:O(1),只创建了一个头节点。

4. 尾插函数-LTPushBack 函数

cs 复制代码
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);

    LTNode* newnode = LTBuyNode(x);
    //phead  phead->prev(尾结点) newnode
    newnode->prev = phead->prev;
    newnode->next = phead;

    phead->prev->next = newnode;
    phead->prev = newnode;
}

- 核心功能:在双向链表的尾部(最后一个有效节点之后)插入一个新节点。

  • 详细说明:

  • 首先通过 assert(phead) 断言 phead 不为 NULL ,确保传入的头节点有效。

  • 调用 LTBuyNode(x) 创建一个数据为 x 的新节点 newnode 。

- 调整新节点的指针:

- 将 newnode->prev 指向当前链表的尾节点(即头节点的 prev 指针,因为头节点的

prev 始终指向最后一个有效节点)。

- 将 newnode->next 指向头节点 phead (使新节点成为新的尾节点,其 next 连接头节

点,维持循环结构)。

- 调整原尾节点和头节点的指针:

- 原尾节点( phead->prev )的 next 指针原本指向头节点,现在需要改为指向新节点

newnode ,使原尾节点与新节点建立连接。

- 头节点 phead 的 prev 指针原本指向原尾节点,现在改为指向新节点 newnode ,确认

新节点为新的尾节点。

  • 插入完成后,链表的尾部成功添加了新节点,且仍保持循环双向结构。

  • 联系:基于头节点找到尾节点(头节点的前驱),通过调用LTBuyNode创建新节点,再调

整相关节点的前驱和后继指针完成插入。

- 时间复杂度:O(1),通过头节点可直接找到尾节点,插入操作仅需固定的指针调整。

- 空间复杂度:O(1),调用LTBuyNode创建一个新节点,空间开销固定。

5. 头插函数-LTPushFront 函数

cs 复制代码
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
    assert(phead);

    LTNode* newnode = LTBuyNode(x);
    //phead newnode phead->next
    newnode->next = phead->next;
    newnode->prev = phead;

    phead->next->prev = newnode;
    phead->next = newnode;
}

- 核心功能:在双向链表的头部(头节点之后)插入一个新节点。

  • 详细说明:

  • 首先通过 assert(phead) 断言 phead 不为 NULL ,确保传入的头节点有效。

  • 调用 LTBuyNode(x) 创建一个数据为 x 的新节点 newnode 。

- 调整新节点的指针:

- 将 newnode->next 指向头节点的 next 指针(即原第一个有效节点的地址)。

- 将 newnode->prev 指向头节点 phead ,使新节点的前驱连接到头节点。

- 调整原第一个有效节点和头节点的指针:

- 原第一个有效节点( phead->next )的 prev 指针原本指向头节点,现在改为指向新节点

newnode ,使原第一个节点与新节点建立连接。

- 头节点 phead 的 next 指针原本指向原第一个有效节点,现在改为指向新节点

newnode ,确认新节点为新的第一个有效节点。

  • 插入完成后,链表的头部成功添加了新节点,且仍保持循环双向结构。

  • 联系:借助头节点的后继指针找到头部位置,调用LTBuyNode创建新节点后调整指针完成

插入。

- 时间复杂度:O(1),直接通过头节点即可定位插入位置,指针调整操作固定。

- 空间复杂度:O(1),仅创建一个新节点。

6. 判空函数-LTEmpty 函数

cs 复制代码
//只有一个头结点的情况下,双向链表为空
bool LTEmpty(LTNode* phead)
{
    assert(phead);
    return phead->next == phead;
}

- 核心功能:判断双向链表是否为空(即除头节点外无其他有效节点)。

  • 详细说明:

  • 首先通过 assert(phead) 断言 phead 不为 NULL ,确保传入的头节点有效。

- 双向链表为空的判断条件:头节点的 next 指针指向自身(因为初始化后的空链表中,头节

点的 next 和 prev 都指向自己,且没有其他有效节点)。

- 函数返回 phead->next == phead 的结果(若为 true 则链表为空,若为 false 则链表非

空)。

- 该函数主要用于删除操作(如 LTPopBack 、 LTPopFront )前的合法性检查,避免对空

链表执行删除操作。

  • 联系:在尾删(LTPopBack)和头删(LTPopFront)操作前调用,防止对空链表进行删除

操作。

- 时间复杂度:O(1),只需判断头节点的后继指针是否指向自身。

- 空间复杂度:O(1),无额外空间开销。

7. 尾删函数-LTPopBack 函数

cs 复制代码
//尾删
void LTPopBack(LTNode* phead)
{
    assert(!LTEmpty(phead));
    LTNode* del = phead->prev;
    //phead del->prev del
    del->prev->next = phead;
    phead->prev = del->prev;

    free(del);
    del = NULL;
}

- 核心功能:删除双向链表的尾部节点(最后一个有效节点)。

  • 详细说明:

  • 首先通过 assert(!LTEmpty(phead)) 断言链表非空(即 LTEmpty(phead) 返回 false ),

防止对空链表执行删除操作。

- 定义指针 del 指向要删除的尾节点,即 phead->prev (头节点的 prev 始终指向最后一

个有效节点)。

- 调整链表指针,跳过待删除节点:

- 将 del->prev->next 指向 phead (即尾节点的前驱节点的 next 原本指向 del ,现在改

为指向头节点,使尾节点的前驱成为新的尾节点)。

- 将 phead->prev 指向 del->prev (即头节点的 prev 原本指向 del ,现在改为指向

del 的前驱节点,确认新尾节点的位置)。

- 释放待删除节点的内存:调用 free(del) 释放 del 指向的节点空间。

- 将 del 置为 NULL (避免出现野指针)。

  • 联系:依赖LTEmpty判断链表是否为空,确保删除操作的安全性,通过头节点找到尾节点后

调整相关指针并释放尾节点内存。

- 时间复杂度:O(1),可直接通过头节点找到尾节点及其前驱,操作固定。

- 空间复杂度:O(1),仅使用少量临时指针变量。

8. 头删-LTPopFront 函数

cs 复制代码
//头删
void LTPopFront(LTNode* phead)
{
    assert(!LTEmpty(phead));
    LTNode* del = phead->next;
    //phead del del->next
    del->next->prev = phead;
    phead->next = del->next;

    free(del);
    del = NULL;
}

- 核心功能:删除双向链表的头部节点(第一个有效节点)。

  • 详细说明:

  • 首先通过 assert(!LTEmpty(phead)) 断言链表非空,防止对空链表执行删除操作。

- 定义指针 del 指向要删除的头节点后的第一个有效节点,即 phead->next 。

- 调整链表指针,跳过待删除节点:

- 将 del->next->prev 指向 phead (即待删除节点的后继节点的 prev 原本指向 del ,现

在改为指向头节点,使该后继节点成为新的第一个有效节点)。

- 将 phead->next 指向 del->next (即头节点的 next 原本指向 del ,现在改为指向

del 的后继节点,确认新的第一个有效节点)。

- 释放待删除节点的内存:调用 free(del) 释放 del 指向的节点空间。

  • 将 del 置为 NULL (避免出现野指针)。

  • 联系:依赖LTEmpty判断链表是否为空,通过头节点找到头部节点后调整指针并释放该节点

内存。

- 时间复杂度:O(1),直接通过头节点找到要删除的节点,操作固定。

- 空间复杂度:O(1),仅使用少量临时指针变量。

9. 查找函数-LTFind 函数

cs 复制代码
//查找函数
LTNode* LTFind(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* pcur = phead->next;
    while (pcur != phead)
    {
        if (pcur->data == x)
        {
            return pcur;
        }
        pcur = pcur->next;
    }
    //没找到
    return NULL;
}

- 核心功能:在双向链表中查找数据域为 x 的节点,返回该节点的指针(若未找到则返回

NULL )。

  • 详细说明:

  • 首先通过 assert(phead) 断言头节点有效。

- 定义临时指针 pcur ,初始化为 phead->next (即第一个有效节点的地址)。

- 循环遍历链表:当 pcur != phead 时,进入循环(遍历所有有效节点)。

- 检查当前节点数据:在循环中,若 pcur->data == x ,则找到目标节点,返回 pcur 。

- 移动指针:若当前节点不是目标节点,将 pcur 更新为 pcur->next ,继续遍历下一个节

点。

- 遍历结束后,若未找到目标节点(即 pcur 循环回到 phead ),则返回 NULL 。

  • 该函数为插入(如 LTInsert )、删除(如 LTErase )等操作提供节点定位功能。

  • 联系:为LTInsert(在指定位置后插入)和LTErase(删除指定位置节点)等函数提供定位

功能,这些函数需要基于找到的节点位置进行操作。

- 时间复杂度:O(n),最坏情况下需要遍历所有节点才能确定是否存在目标节点。

- 空间复杂度:O(1),只使用了一个临时指针变量。

10. 插入函数-LTInsert 函数

cs 复制代码
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x)
{
    assert(pos);
    LTNode* newnode = LTBuyNode(x);
    //pos newnode pos->next
    newnode->next = pos->next;
    newnode->prev = pos;

    pos->next->prev = newnode;
    pos->next = newnode;
}

- 核心功能:在双向链表中指定节点 pos 的后面插入一个新节点。

  • 详细说明:

  • 首先通过 assert(pos) 断言 pos 不为 NULL ,确保插入位置有效。

  • 调用 LTBuyNode(x) 创建一个数据为 x 的新节点 newnode 。

- 调整新节点的指针:

- 将 newnode->next 指向 pos->next (即新节点的后继为 pos 原本的后继节点)。

- 将 newnode->prev 指向 pos (即新节点的前驱为 pos 节点)。

- 调整 pos 后继节点和 pos 自身的指针:

- 将 pos->next->prev 指向 newnode (即 pos 原本的后继节点的前驱,现在改为指向新

节点)。

- 将 pos->next 指向 newnode (即 pos 的后继改为新节点)。

- 插入完成后,新节点成功位于 pos 和 pos 原后继节点之间,链表仍保持循环双向结构。

  • 联系:调用LTBuyNode创建新节点,通过调整pos、新节点和pos后继节点的指针关系完成

插入,常与LTFind配合使用,在找到的节点后插入新数据。

- 时间复杂度:O(1),已知pos位置后,插入操作仅需固定的指针调整。

- 空间复杂度:O(1),仅创建一个新节点。

11. 删除函数-LTErase 函数

cs 复制代码
//删除pos位置的结点
void LTErase(LTNode* pos)
{
    assert(pos);
    //pos->prev pos pos->next
    pos->next->prev = pos->prev;
    pos->prev->next = pos->next;

    free(pos);
    pos = NULL;
}

- 核心功能:删除双向链表中指定位置 pos 的节点。

  • 详细说明:

  • 首先通过 assert(pos) 断言 pos 不为 NULL ,确保删除位置有效。

-调整链表指针,跳过待删除节点 pos :

- 将 pos->next->prev 指向 pos->prev (即 pos 后继节点的前驱,改为指向 pos 的前

驱节点)。

- 将 pos->prev->next 指向 pos->next (即 pos 前驱节点的后继,改为指向 pos 的后

继节点)。

- 释放待删除节点的内存:调用 free(pos) 释放 pos 指向的节点空间。

- 将 pos 置为 NULL (避免出现野指针)。

- 该函数需配合 LTFind 使用,先通过 LTFind 找到目标节点,再调用 LTErase 删除。

  • 联系:通过调整pos的前驱节点和后继节点的指针关系,释放pos节点的内存,常与LTFind

配合使用,删除找到的节点。

- 时间复杂度:O(1),已知pos位置后,删除操作仅需固定的指针调整和内存释放。

- 空间复杂度:O(1),无额外空间开销(除了临时指针)。

12. 销毁函数-LTDesTroy 函数

cs 复制代码
void LTDesTroy(LTNode* phead)
{
    LTNode* pcur = phead->next;
    while (pcur != phead)
    {
        LTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    free(phead);
    phead = NULL;
}

- 核心功能:销毁双向链表,释放所有节点(包括头节点)的内存空间,避免内存泄漏。

  • 详细说明:

  • 定义指针 pcur ,初始化为 phead->next (即第一个有效节点的地址)。

- 循环释放有效节点:当 pcur != phead 时,进入循环(遍历所有有效节点)。

- 保存下一个节点地址:在循环中,定义指针 next 指向 pcur->next (避免释放 pcur 后

无法找到下一个节点)。

- 释放当前节点:调用 free(pcur) 释放 pcur 指向的节点空间。

- 移动指针:将 pcur 更新为 next ,继续释放下一个节点。

- 所有有效节点释放完成后,释放头节点 phead 的内存空间(调用 free(phead) )。

- 将 phead 置为 NULL (避免出现野指针)。

  • 注意:该函数调用后,需在外部手动将链表指针(如 plist )置为 NULL (如测试文件中

LTDesTroy(plist); 后执行 plist = NULL; )。

  • 联系:在链表使用完毕后调用,避免内存泄漏,需要遍历链表的所有节点进行释放。

- 时间复杂度:O(n),n为链表中所有节点(包括头节点)的总数,需要逐个释放每个节点

**。

  • 空间复杂度:O(1),只使用了少量临时指针变量。**

函数间的联系总结:

这些函数相互配合,共同实现了双向链表的完整功能。LTInit初始化链表后,LTPushBack、

LTPushFront等插入函数用于构建链表;LTPrint用于查看链表内容;LTFind用于定位节点,为

LTInsert和LTErase提供位置信息;LTPopBack、LTPopFront、LTErase用于删除节点;

LTEmpty保障删除操作的安全性;LTDesTroy在链表使用结束后释放资源。LTBuyNode作为基础

工具函数,被多个插入类函数调用,负责节点的创建。

1.3 测试文件(test.c)

cs 复制代码
#include"List.h"

void test01()
{
    //LTNode* plist = NULL;
    //LTInit(&plist);

    LTNode* plist = LTInit();
    LTPushBack(plist, 1);
    LTPushBack(plist, 2);
    LTPushBack(plist, 3);
    LTPushBack(plist, 4);
    //LTPushFront(plist, 1);
    //LTPushFront(plist, 2);
    LTPrint(plist);

    //LTPopBack(plist);
    //LTPrint(plist);
    //LTPopBack(plist);
    //LTPrint(plist);
    //LTPopBack(plist);
    //LTPrint(plist);
    //LTPopBack(plist);
    //LTPrint(plist);
    //LTPopFront(plist);
    //LTPrint(plist);
    //LTPopFront(plist);
    //LTPrint(plist);
    //LTPopFront(plist);
    //LTPrint(plist);
    //LTPopFront(plist);
    //LTPrint(plist);

    //LTNode* find = LTFind(plist, 1);
    //if (find == NULL)
    //{
    //    printf("δҵ\n");
    //}
    //else {
    //    printf("ҵˣ\n");
    //}
    //LTInsert(find, 100);
    //LTErase(find);
    //LTPrint(plist);


    //LTDesTroy(&plist);
    LTDesTroy(plist);
    plist = NULL;
}

int main()
{
    test01();
    return 0;
}
  • 测试文件(如test.c):包含测试函数(如test01)和main函数,通过调用List.c中实现的函数,

对双向链表的各种功能进行测试,验证其正确性。

以上便是关于双向链表代码实现的所有内容。

下面小编把完整版的代码内容留给大家:

List.h文件:

cs 复制代码
#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;
}LTNode;


void LTPrint(LTNode* phead);
//双向链表的初始化
//void LTInit(LTNode** pphead);

LTNode* LTInit();
//传二级:违背了接口一致性
//void LTDesTroy(LTNode** pphead);
//传一级:调用完成之后将实参手动置为NULL(推荐)
void LTDesTroy(LTNode* phead);

//头结点要发生改变,传二级
// 头结点不发生改变,传一级
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);

bool LTEmpty(LTNode* phead);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);

LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x);
//课下练习------在指定位置之前插入数据

//删除pos位置的结点
void LTErase(LTNode* pos);

List.c文件:

cs 复制代码
#include"List.h"

LTNode* LTBuyNode(LTDataType x)
{
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    if (newnode == NULL)
    {
        perror("malloc fail!");
        exit(1);
    }
    newnode->data = x;
    newnode->next = newnode->prev = newnode;

    return newnode;
}

////双向链表的初始化
//void LTInit(LTNode** pphead)
//{
//    assert(pphead);
//    *pphead = LTBuyNode(-1);
//}

void LTPrint(LTNode* phead)
{
    LTNode* pcur = phead->next;
    while (pcur != phead)
    {
        printf("%d -> ", pcur->data);
        pcur = pcur->next;
    }
    printf("\n");
}

LTNode* LTInit()
{
    LTNode* phead = LTBuyNode(-1);
    return phead;
}

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);

    LTNode* newnode = LTBuyNode(x);
    //phead  phead->prev(尾结点) newnode
    newnode->prev = phead->prev;
    newnode->next = phead;

    phead->prev->next = newnode;
    phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
    assert(phead);

    LTNode* newnode = LTBuyNode(x);
    //phead newnode phead->next
    newnode->next = phead->next;
    newnode->prev = phead;

    phead->next->prev = newnode;
    phead->next = newnode;
}
//只有一个头结点的情况下,双向链表为空
bool LTEmpty(LTNode* phead)
{
    assert(phead);
    return phead->next == phead;
}
//尾删
void LTPopBack(LTNode* phead)
{
    assert(!LTEmpty(phead));
    LTNode* del = phead->prev;
    //phead del->prev del
    del->prev->next = phead;
    phead->prev = del->prev;

    free(del);
    del = NULL;
}

//头删
void LTPopFront(LTNode* phead)
{
    assert(!LTEmpty(phead));
    LTNode* del = phead->next;
    //phead del del->next
    del->next->prev = phead;
    phead->next = del->next;

    free(del);
    del = NULL;
}

LTNode* LTFind(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* pcur = phead->next;
    while (pcur != phead)
    {
        if (pcur->data == x)
        {
            return pcur;
        }
        pcur = pcur->next;
    }
    //没找到
    return NULL;
}
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x)
{
    assert(pos);
    LTNode* newnode = LTBuyNode(x);
    //pos newnode pos->next
    newnode->next = pos->next;
    newnode->prev = pos;

    pos->next->prev = newnode;
    pos->next = newnode;
}
//删除pos位置的结点
void LTErase(LTNode* pos)
{
    assert(pos);
    //pos->prev pos pos->next
    pos->next->prev = pos->prev;
    pos->prev->next = pos->next;

    free(pos);
    pos = NULL;
}

//void LTDesTroy(LTNode** pphead)
//{
//    LTNode* pcur = (*pphead)->next;
//    while (pcur != *pphead)
//    {
//        LTNode* next = pcur->next;
//        free(pcur);
//        pcur = next;
//    }
//    free(*pphead);
//    *pphead = NULL;
//}
void LTDesTroy(LTNode* phead)
{
    LTNode* pcur = phead->next;
    while (pcur != phead)
    {
        LTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    free(phead);
    phead = NULL;
}

test.c文件:

cs 复制代码
#include"List.h"

void test01()
{
    //LTNode* plist = NULL;
    //LTInit(&plist);

    LTNode* plist = LTInit();
    LTPushBack(plist, 1);
    LTPushBack(plist, 2);
    LTPushBack(plist, 3);
    LTPushBack(plist, 4);
    //LTPushFront(plist, 1);
    //LTPushFront(plist, 2);
    LTPrint(plist);

    //LTPopBack(plist);
    //LTPrint(plist);
    //LTPopBack(plist);
    //LTPrint(plist);
    //LTPopBack(plist);
    //LTPrint(plist);
    //LTPopBack(plist);
    //LTPrint(plist);
    //LTPopFront(plist);
    //LTPrint(plist);
    //LTPopFront(plist);
    //LTPrint(plist);
    //LTPopFront(plist);
    //LTPrint(plist);
    //LTPopFront(plist);
    //LTPrint(plist);

    //LTNode* find = LTFind(plist, 1);
    //if (find == NULL)
    //{
    //    printf("δҵ\n");
    //}
    //else {
    //    printf("ҵˣ\n");
    //}
    //LTInsert(find, 100);
    //LTErase(find);
    //LTPrint(plist);


    //LTDesTroy(&plist);
    LTDesTroy(plist);
    plist = NULL;
}

int main()
{
    test01();
    return 0;
}

1.4 总结:

  1. 结构本质:以节点为基本单位,每个节点含数据域及两个指针域(分别指向前后节点),形成可

双向访问的线性结构,常结合哨兵位头节点实现循环设计以简化操作。

  1. 核心优势:
  • 支持双向遍历,可从任意节点向前后方向访问。

  • 插入、删除操作效率高(已知位置时为O(1)),无需像单链表那样遍历查找前驱节点。

  • 哨兵位头节点的设计统一了空链表与非空链表的操作逻辑,减少边界条件判断。

  1. 主要局限:
  • 每个节点需额外存储一个指针,空间开销高于单链表。

  • 操作时需同时维护 prev 和 next 两个指针,逻辑复杂度略高,易出现指针指向错误。

  1. 适用场景:需频繁进行双向遍历、首尾操作或已知位置增删的场景(如双向队列、浏览器历史记

录等)。

  1. 操作核心:所有插入、删除操作的本质是通过调整节点的 prev 和 next 指针,维持链表的双向连

接关系,循环结构下需保证首尾指针的闭环性。

以上就是关于双向链表的所有所有内容。小编也是写了很长时间,如果有错误,希望大家能够指

出。也非常感谢大家的观看!