当前位置: 首页 > news >正文

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

目录

1.双向链表的实现


1.双向链表的实现

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

(.h) , 一个实现文件(.c)和一个测试文件(.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. 节点创建函数-LTBuyNode 函数

#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 函数

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 函数

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

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


- 详细说明:


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

节点的占位数据)。


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

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

始状态。


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


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


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


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


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

4. 尾插函数-LTPushBack 函数

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);//phead  phead->prev(尾结点) newnodenewnode->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 函数

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);//phead newnode phead->nextnewnode->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 函数

//只有一个头结点的情况下,双向链表为空
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 函数

//尾删
void LTPopBack(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->prev;//phead del->prev deldel->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 函数

//头删
void LTPopFront(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->next;//phead del del->nextdel->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 函数

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

//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);//pos newnode pos->nextnewnode->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 函数

//删除pos位置的结点
void LTErase(LTNode* pos)
{assert(pos);//pos->prev pos pos->nextpos->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 函数

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(尾结点) newnodenewnode->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->nextnewnode->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 deldel->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->nextdel->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->nextnewnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
//删除pos位置的结点
void LTErase(LTNode* pos)
{assert(pos);//pos->prev pos pos->nextpos->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 指针,维持链表的双向连

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

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

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

http://www.dtcms.com/a/283160.html

相关文章:

  • 黄仁勋链博会演讲实录:脱掉皮衣,穿上唐装,中文开场
  • 完善评论发布功能
  • PHP面向对象编程:类与对象的基础概念与实践
  • 从0到1搭建Lazada账号矩阵:自养号测评的精细化养号全攻略
  • Linux 定时器应用示例
  • 功能测试和回归测试
  • C# WPF后台设置控件样式失效的解决方法
  • 【Vue】tailwindcss + ant-design-vue + vue-cropper 图片裁剪功能(解决遇到的坑)
  • 从规模到效率:大模型三大定律与Chinchilla定律详解
  • 实现通讯录人员选择
  • IKE学习笔记
  • Java强化:多线程及线程池
  • 从电子管到CPU
  • 基于MATLAB的决策树DT的数据分类预测方法应用
  • Android CameraX使用
  • [析]Deep reinforcement learning for drone navigation using sensor data
  • CClink IEF Basic设备数据 保存到MySQL数据库项目案例
  • 高德地图MCP服务使用案例
  • 解锁数据交换的魔法工具——Protocol Buffers
  • 矿业自动化破壁者:EtherCAT转PROFIBUS DP网关的井下实战
  • ABP VNext + EF Core 二级缓存:提升查询性能
  • Mysql系列--1、库的相关操作
  • Mybatis-2快速入门
  • @Binds/@IntoMap/@ClassKey的使用
  • C++ shared_ptr 底层实现分析
  • uniapp+vue3+鸿蒙系统的开发
  • WD5018 同步整流降压转换器核心特性与应用,电压12V降5V,2A电流输出
  • MySQL学习——面试版
  • ssl相关命令生成证书
  • LangChain面试内容整理-知识点21:LangSmith 调试与监控平台