数据结构 双向链表(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 指针,维持链表的双向连
接关系,循环结构下需保证首尾指针的闭环性。
以上就是关于双向链表的所有所有内容。小编也是写了很长时间,如果有错误,希望大家能够指
出。也非常感谢大家的观看!