目录

1.1 头文件(list.h)

1.2 实现文件(List.c)

1. 节点创建函数

2. 链表打印函数

3. 链表初始化函数

4. 尾插函数

5. 头插函数

6. 判空函数

7. 尾删函数

8. 头删函数

9. 查找函数

10. 插入函数

11. 删除函数

12. 销毁函数

1.3 测试文件(test.c)

1.4 总结:


实现双向链表和实现单链表的步骤一样,小编也是用三个文件编写完成的。分别是一个头文件(List.h) , 一个实现文件(List.c)和一个测试文件(test.c)。 下面对这三个文件的代码按照功能模块,函数逻辑等进行详细解释,进一步帮助理解单链表的实现。

1.1 头文件(list.h)

- List.h(头文件):定义双向链表的结构体、函数声明,包含必要的头文件(如stdio.h、stdlib.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;
}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. 节点创建函数

#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. 链表打印函数

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. 链表初始化函数

初始化方法一 : 通过二级指针修改头指针

void LTInit(LTNode** pphead)
{
	assert(pphead);
	*pphead = LTBuyNode(-1);
}

//测试文件中的调用逻辑: 
LTNode* plist = NULL;  // 先定义一个空指针
LTInit(&plist);        // 传递指针的地址,让函数内部修改plist的指向
  • 由于要修改外部指针变量(如  plist )的指向,需要传递指针的地址(即二级指针  pphead )。
  • 函数内先断言  pphead  非空,再为头指针分配一个头结点(通过  LTBuyNode(-1)  创建),并将头结点的地址赋值给  *pphead (即外部的  plist )。

初始化方法二:通过函数返回值直接返回头结点地址(推荐)

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

//测试文件中的调用逻辑:
LTNode* plist = LTInit();  // 直接接收函数返回的头结点地址
  • 调用  LTBuyNode  函数创建一个新节点,传入的参数为  -1 (该值无实际意义,仅作为头节点的占位数据)。
  • 由于  LTBuyNode  函数会将新节点的  next  和  prev  都指向自身,因此创建的头节点会形成一个自循环的结构(即头节点的  next  和  prev  都指向自己),这是双向循环链表的初始状态。
  • 返回创建的头节点指针,作为初始化后链表的入口。
  • 后续对链表的所有操作(如插入、删除等)都以该头节点为基准进行。

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

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

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

  • 方法一利用二级指针实现“修改外部指针的指向”,适合C语言中对指针本身的修改场景。
  • 方法二利用函数返回值直接传递头结点地址,代码更简洁直观,是现代C语言开发中更推荐的风格之一。

4. 尾插函数

//尾插
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 ,确认新节点为新的尾节点。

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


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

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

5. 头插函数

//头插
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 ,确认新节点为新的第一个有效节点。

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


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

6. 判空函数

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

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

  • 首先通过  assert(phead)  断言  phead  不为  NULL ,确保传入的头节点有效。
  • 双向链表为空的判断条件:头节点的  next  指针指向自身(因为初始化后的空链表中,头节点的  next  和  prev  都指向自己,且没有其他有效节点)。
  • 函数返回  phead->next == phead  的结果(若为  true  则链表为空,若为  false  则链表非空)。
  • 该函数主要用于删除操作(如  LTPopBack 、 LTPopFront )前的合法性检查,避免对空链表执行删除操作。

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

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

7. 尾删函数

//尾删
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. 头删函数

//头删
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. 查找函数

//查找函数
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(删除指定位置节点)等函数提供定位

功能,这些函数需要基于找到的节点位置进行操作。
时间复杂度:O(n),最坏情况下需要遍历所有节点才能确定是否存在目标节点。
空间复杂度:O(1),只使用了一个临时指针变量。

10. 在pos位置前插入函数

//在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. pos位置删除函数

//删除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  删除

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

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

12. 销毁函数

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)

#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文件:

#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文件:

#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文件:

#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. 结构本质:以节点为基本单位,每个节点含数据域及两个指针域(分别指向前后节点),形成可双向访问的线性结构,常结合哨兵位头节点实现循环设计以简化操作。

2. 核心优势:

  • 支持双向遍历,可从任意节点向前后方向访问。
  • 插入、删除操作效率高(已知位置时为O(1)),无需像单链表那样遍历查找前驱节点。
  • 哨兵位头节点的设计统一了空链表与非空链表的操作逻辑,减少边界条件判断。

3. 主要局限:

  • 每个节点需额外存储一个指针,空间开销高于单链表。
  • 操作时需同时维护 prev 和 next 两个指针,逻辑复杂度略高,易出现指针指向错误。

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

5. 操作核心:所有插入、删除操作的本质是通过调整节点的 prev 和 next 指针,维持链表的双向连接关系,循环结构下需保证首尾指针的闭环性。

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

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐