> 技术文档 > 【数据结构与算法】数据结构初阶:详解顺序表和链表(五)——双向链表_链表、双向链表

【数据结构与算法】数据结构初阶:详解顺序表和链表(五)——双向链表_链表、双向链表

 


🔥个人主页:艾莉丝努力练剑

❄专栏传送门:《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题

🍉学习方向:C/C++方向

⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平


 


前言:本篇文章,我们继续来看顺序表和链表相关的知识点,在初阶的数据结构与算法阶段,我们把知识点分成三部分,复杂度作为第一部分,顺序表和链表、栈和队列、二叉树为第二部分,排序为第二部分,我们之前已经介绍完了第一部分:算法复杂度,本文我们继续学习第二部分中的顺序表和链表部分内容啦。


再次提醒:为什么我们要学那么多的数据结构?这是因为没有一种数据结构能够去应对所有场景。我们在不同的场景需要选择不同的数据结构,所以数据结构没有谁好谁坏之分,而评估数据结构的好坏要针对场景,如果在一种场景下我们需要频繁地对头部进行插入删除操作,那么这个时候我们用链表;但是如果对尾部进行插入删除操作比较频繁,那我们用顺序表比较好。

        因此,不同的场景我们选择不同的数据结构。


目录

正文 

四、双向链表

(一)链表的分类

1、带头或者不带头

2、单向或者双向

3、循环或者不循环

(二)带头双向循环链表节点的概念与结构

1、概念

2、结构

3、双向链表为空

(三)实现双向链表

1、初始化

2、打印 

3、遍历

4、判断链表是否为空

5、增删查改功能的实现

(1)尾插

(2)头插

(3)在指定位置之前插入数据

(4)在指定位置之后插入数据

(1)尾删

(2)头删

(3)删除pos位置节点

(1)查找

改 

(1)修改

6、销毁链表

7、完整代码

8、优化版代码(考虑接口的一致性) 

(1)初始化优化版

(2)销毁优化版

9、优化后的完整代码

五、顺序表和链表的对比分析

结尾


正文 

四、双向链表

(一)链表的分类

链表并不是一个具体的结构,链表的结构非常多样,下面情况组合起来就有8(2*2*2)种链表结构:

1、带头或者不带头

带头链表中的头节点,不存储任何有效的数据,只用来占位子,“哨兵位” 。

在前面介绍单链表的时候,有的地方博主也会表述成“头节点”,这个称呼是为了方便uu们理解,实际上把单链表中第一个节点称为“头节点”是错误的表述。

2、单向或者双向

3、循环或者不循环

虽然有这么多的链表结构,但是我们实际中最常用的还是两种结构:单链表和双向链表。

(二)带头双向循环链表节点的概念与结构

1、概念

注意:这里的“带头”跟前面我们说的“头结点”是两个概念,实际前面的在单链表阶段称呼并不严谨,但是为了uu们更好的理解就直接称为单链表的头结点。

2、结构

带头链表里的头节点,实际为“哨兵位”哨兵位节点不存储任何有效元素,只是在这里“放哨的”。 

双向链表中由一个一个的节点组成,这里的节点有三个组成部分—— 

struct ListNode{int data;struct ListNode* next;//指向后一个节点的指针struct ListNode* prev;//指向前一个节点的指针}

3、双向链表为空

双向链表为空——指向自己

 

(三)实现双向链表

1、初始化

(1)List.h

#pragma once#include#include//定义双向链表结构typedef int LTDataType;typedef struct ListNode{LTDataType data;struct ListNode* next;//指向后一个节点的指针struct ListNode* prev;//指向前一个节点的指针}LTNode;//要改变头节点plist——要用二级指针//初始化void LTInit(LTNode** pphead);

(2)List.c:

#define _CRT_SECURE_NO_WARNINGS 1#include\"List.h\"//初始化void LTInit(LTNode** pphead){*pphead = (LTNode*)malloc(sizeof(LTNode));if (*pphead == NULL){perror(\"malloc fail!\");exit(1);}(*pphead)->data = -1;(*pphead)->next = (*pphead)->prev = *pphead;}

(3)test.c:

#define _CRT_SECURE_NO_WARNINGS 1#include\"List.h\"void test01(){LTNode* plist = NULL;LTInit(&plist);}int main(){test01();return 0;}

调试一下,打开监视,进入函数(F11)——

像这样,初始化就完成了—— 

补充:博主并不建议把LTNode* plist改成LTNode plist(不用**),很多教材是这样写的,看起来很困难,还存在一个误导的作用。本身大家对指针的理解就很困难了,如果我们这样一会儿加一个一级指针一会儿定义成LTNode,那大家后续去使用是很混乱的,所以不建议这样使用。

        而且关键是后面我们基本用到的都是一级指针,只需要一个*就好了,所以建议大家如果教材中有上面这种写法,不要用,我们就搞一个二级指针就好了。

2、打印 

List.c:

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

test.c:

LTPrint(plist);
3、遍历

4、判断链表是否为空

List.c:

//判断链表是否为空bool LTEmpty(LTNode* phead){assert(phead);return phead->next == phead;}
5、增删查改功能的实现
(1)尾插

哨兵位在双向链表始终不会发生改变——

plist本来指向0x100,有了值之后plist指向0x800。

List.c:

//尾插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;}

test.c:

void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);}int main(){test01();return 0;}

尾插的时间复杂度:O(1) 。 

打开监视,观察一下尾插—— 

(2)头插

List.c:

//头插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;}

test.c:

void test01(){LTNode* plist = NULL;LTInit(&plist);//LTPushBack(plist, 1);//LTPushBack(plist, 2);//LTPushBack(plist, 3);//LTPushBack(plist, 4);LTPushFront(plist, 1);LTPushFront(plist, 2);LTPushFront(plist, 3);LTPushFront(plist, 4);}int main(){test01();return 0;}

头插的时间复杂度:O(1) 。

打开监视看一下——

(3)在指定位置之前插入数据

pos不可能指向phead,因为phead不存储任何有效数据(LTFind找不到)。

List.c:

//在pos位置之前插入节点void LTInsertBefore(LTNode* pos, LTDataType x){assert(pos);LTNode* newnode = LTBuyNode(x);//pos->prev newnode posnewnode->prev = pos->prev;newnode->next = pos;pos->prev->next = newnode;pos->prev = newnode;}

test.c:

void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);LTNode* pos = LTFind(plist,1);LTInsertBefore(pos, 100);LTPrint(plist);}int main(){test01();return 0;}

在指定位置之前插入数据时间复杂度:O(N)。

测试一下—— 

(4)在指定位置之后插入数据

List.c:

//在pos位置之后插入节点void LTInsert(LTNode* pos, LTDataType x)//任意位置的插入{assert(pos);LTNode* newnode = LTBuyNode(x);//pos newnode pos->nextnewnode->prev = pos;newnode->next = pos->next;pos->next->prev = newnode;pos->next = newnode;}

test.c:

void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);LTNode* pos = LTFind(plist,2);LTInsert(pos, 100);LTPrint(plist);}int main(){test01();return 0;}

在指定位置之后插入数据时间复杂度:O(1)。 

这里如果给个这个位置——

LTNode* pos = LTFind(plist,4);

就相当于尾插,头插行不行呢?头插是在头节点之后插入,不行,这里只能给1。 

(1)尾删

List.c:

//尾删void LTPopBack(LTNode* phead){assert(!LTEmpty(phead));LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;//其实置不置为空都没关系,这里执行到这里程序已经结束了,不会再用到del了//这样写无非是养成一个好习惯}

这里其实置不置为空都没关系,这里执行到这里程序已经结束了,不会再用到del了,我们这样写无非是养成一个好习惯而已。

test.c:

void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);//LTPushFront(plist, 1);//LTPushFront(plist, 2);//LTPushFront(plist, 3);//LTPushFront(plist, 4);LTPopBack(plist);LTPrint(plist);LTPopBack(plist);LTPrint(plist);LTPopBack(plist);LTPrint(plist);LTPopBack(plist);LTPrint(plist);}int main(){test01();return 0;}

尾删的时间复杂度:O(1) 。 

这里再删一次就会断言报错了(我们的期望)。

(2)头删

List.c:

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

test.c:

void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);LTPopFront(plist);LTPrint(plist);LTPopFront(plist);LTPrint(plist);LTPopFront(plist);LTPrint(plist);LTPopFront(plist);LTPrint(plist);}int main(){test01();return 0;}

头删的时间复杂度:O(1) 。

再删一次就会断言报错。

(3)删除pos位置节点

这里没有传phead。

List.c:

//删除pos位置的节点void LTErase(LTNode* pos){assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;}

test.c:

void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);LTNode* pos = LTFind(plist,2);LTErase(pos);LTPrint(plist);}int main(){test01();return 0;}
(1)查找

如果找到了,把这个节点的指针返回,既然是查找,无非就是遍历;

如果没找到,返回一个NULL就好了。

List.c:

//在双向链表里面查找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;}

test.c:

void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);LTNode* pos = LTFind(plist,4);if (pos){printf(\"找到了!\\n\");}else{printf(\"未找到!\\n\");}}int main(){test01();return 0;}

测试一下——

我们来测试一下查找一个链表中没有的值—— 

改 
(1)修改

修改就很简单了,我们不做过多介绍。

List.c:

//修改void LTChange(LTNode* pos, LTDataType x){assert(pos);pos->data = x;}

test.c:

#include\"List.h\"void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);LTNode* pos = LTFind(plist,2);//修改LTChange(pos, 100);LTPrint(plist);}int main(){test01();return 0;}
6、销毁链表

哨兵位phead也要销毁,传二级指针**pphead。

List.c:

//销毁void LTDestory(LTNode** pphead){LTNode* pcur = (*pphead)->next;while(pcur != *pphead){LTNode* next = pcur->next;free(pcur);pcur = next;}//销毁头节点free(*pphead);*pphead = NULL;}

test.c:

void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);LTNode* pos = LTFind(plist,2);LTErase(pos);LTPrint(plist);//销毁LTDestory(&plist);}int main(){test01();return 0;}

打开监视观察一下—— 

7、完整代码

(1)List.h

#pragma once#include#include#include#include//定义双向链表结构typedef int LTDataType;typedef struct ListNode{LTDataType data;struct ListNode* next;//指向后一个节点的指针struct ListNode* prev;//指向前一个节点的指针}LTNode;//要改变头节点plist——要用二级指针//初始化void LTInit(LTNode** pphead);//销毁void LTDestory(LTNode** pphead);//在双向链表中,增删查改都不会改变哨兵位节点//所以我们要用一级指针,因为我们不改变phead——plist指针//尾插void LTPushBack(LTNode* phead, LTDataType x);//头插void LTPushFront(LTNode* phead, LTDataType x);//尾删void LTPopBack(LTNode* phead);//判断链表是否为空bool LTEmpty(LTNode* phead);//打印void LTPrint(LTNode* phead);//头删void LTPopFront(LTNode* phead);//在双向链表里面查找LTNode* LTFind(LTNode* phead, LTDataType x);//在pos位置之前插入节点void LTInsertBefore(LTNode* pos, LTDataType x);//在pos位置之后插入节点void LTInsert(LTNode* pos, LTDataType x);//任意位置的插入//删除pos位置的节点void LTErase(LTNode* pos);//修改void LTChange(LTNode* pos, LTDataType x);

(2)List.c:

#define _CRT_SECURE_NO_WARNINGS 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;}//第一版初始化void LTInit(LTNode** pphead){//*pphead = (LTNode*)malloc(sizeof(LTNode));//if (*pphead == NULL)//{//perror(\"malloc fail!\");//exit(1);//}//(*pphead)->data = -1;//(*pphead)->next = (*pphead)->prev = *pphead;*pphead = LTBuyNode(-1);//申请一个哨兵位节点}//销毁void LTDestory(LTNode** pphead){LTNode* pcur = (*pphead)->next;while(pcur != *pphead){LTNode* next = pcur->next;free(pcur);pcur = next;}//销毁头节点free(*pphead);*pphead = NULL;}//尾插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;}//打印void LTPrint(LTNode* phead){LTNode* pcur = phead->next;while (pcur != phead){printf(\"%d -> \", pcur->data);pcur = pcur->next;}printf(\"\\n\");}//判断链表是否为空bool LTEmpty(LTNode* phead){assert(phead);return phead->next == phead;}//尾删void LTPopBack(LTNode* phead){assert(!LTEmpty(phead));LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;//其实置不置为空都没关系,这里执行到这里程序已经结束了,不会再用到del了//这样写无非是养成一个好习惯}//头删void LTPopFront(LTNode* phead){assert(!LTEmpty(phead));LTNode* del = phead->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 LTInsertBefore(LTNode* pos, LTDataType x){assert(pos);LTNode* newnode = LTBuyNode(x);//pos->prev newnode posnewnode->prev = pos->prev;newnode->next = pos;pos->prev->next = newnode;pos->prev = newnode;}//在pos位置之后插入节点void LTInsert(LTNode* pos, LTDataType x)//任意位置的插入{assert(pos);LTNode* newnode = LTBuyNode(x);//pos newnode pos->nextnewnode->prev = pos;newnode->next = pos->next;pos->next->prev = newnode;pos->next = newnode;}//删除pos位置的节点void LTErase(LTNode* pos){assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;}//修改void LTChange(LTNode* pos, LTDataType x){assert(pos);pos->data = x;}

(3)test.c:

#define _CRT_SECURE_NO_WARNINGS 1#include\"List.h\"void test01(){LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);//LTPushFront(plist, 1);//LTPushFront(plist, 2);//LTPushFront(plist, 3);//LTPushFront(plist, 4);// //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);//LTPopFront(plist);//LTPrint(plist);LTNode* pos = LTFind(plist,2);//if (pos)//{//printf(\"找到了!\\n\");//}//else//{//printf(\"未找到!\\n\");//}// //LTInsertBefore(pos, 100);//LTPrint(plist);//LTInsert(pos, 100);//LTPrint(plist);//LTErase(pos);//LTPrint(plist);//修改LTChange(pos, 100);LTPrint(plist);//销毁LTDestory(&plist);}int main(){test01();return 0;}
8、优化版代码(考虑接口的一致性) 

我们的程序是要给使用用户使用的。

考虑到接口的一致性,即一级指针就全部一级指针,二级指针就全部二级指针,一会儿一级指针一会儿又二级指针,会增加使用用户的记忆成本。

(1)初始化优化版

List.c:

//优化版初始化LTNode* LTInit(){LTNode* phead = LTBuyNode(-1);return phead;}

test.c:

void test01(){//LTNode* plist = NULL;//LTInit(&plist);LTNode* plist = LTInit();//创建一个指针来接收初始化的返回值LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);LTNode* pos = LTFind(plist,2);//修改LTChange(pos, 100);LTPrint(plist);//销毁LTDestory(&plist);}int main(){test01();return 0;}

现在初始化优化完了,满足了接口的一致性。

(2)销毁优化版

List.c:

//优化版销毁void LTDestroy(LTNode* phead){LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//销毁头结点free(phead);phead = NULL;}

空间全部释放完plist会变野指针,最后实参会变成一个随机数,我们要把实参手动置为空。 

test.c:

void test01(){//LTNode* plist = NULL;//LTInit(&plist);LTNode* plist = LTInit();//创建一个指针来接收初始化的返回值LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);LTNode* pos = LTFind(plist,2);//修改LTChange(pos, 100);LTPrint(plist);//优化版销毁LTDestroy(plist);plist = NULL;}int main(){test01();return 0;}

优化的部分就是原先用了二级指针的初始化销毁

9、优化后的完整代码
(1)List.h
#pragma once#include#include#include#include//定义双向链表结构typedef int LTDataType;typedef struct ListNode{LTDataType data;struct ListNode* next;//指向后一个节点的指针struct ListNode* prev;//指向前一个节点的指针}LTNode;//优化版初始化LTNode* LTInit();//优化版销毁——为了保持接口一致性void LTDestroy(LTNode* phead);//在双向链表中,增删查改都不会改变哨兵位节点//所以我们要用一级指针,因为我们不改变phead——plist指针//尾插void LTPushBack(LTNode* phead, LTDataType x);//头插void LTPushFront(LTNode* phead, LTDataType x);//尾删void LTPopBack(LTNode* phead);//判断链表是否为空bool LTEmpty(LTNode* phead);//打印void LTPrint(LTNode* phead);//头删void LTPopFront(LTNode* phead);//在双向链表里面查找LTNode* LTFind(LTNode* phead, LTDataType x);//在pos位置之前插入节点void LTInsertBefore(LTNode* pos, LTDataType x);//在pos位置之后插入节点void LTInsert(LTNode* pos, LTDataType x);//任意位置的插入//删除pos位置的节点void LTErase(LTNode* pos);//修改void LTChange(LTNode* pos, LTDataType x);
(2)List.c:
#define _CRT_SECURE_NO_WARNINGS 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;}//优化版初始化LTNode* LTInit(){LTNode* phead = LTBuyNode(-1);return phead;}//优化版销毁void LTDestroy(LTNode* phead){LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//销毁头结点free(phead);phead = NULL;}//尾插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;}//打印void LTPrint(LTNode* phead){LTNode* pcur = phead->next;while (pcur != phead){printf(\"%d -> \", pcur->data);pcur = pcur->next;}printf(\"\\n\");}//判断链表是否为空bool LTEmpty(LTNode* phead){assert(phead);return phead->next == phead;}//尾删void LTPopBack(LTNode* phead){assert(!LTEmpty(phead));LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;//其实置不置为空都没关系,这里执行到这里程序已经结束了,不会再用到del了//这样写无非是养成一个好习惯}//头删void LTPopFront(LTNode* phead){assert(!LTEmpty(phead));LTNode* del = phead->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 LTInsertBefore(LTNode* pos, LTDataType x){assert(pos);LTNode* newnode = LTBuyNode(x);//pos->prev newnode posnewnode->prev = pos->prev;newnode->next = pos;pos->prev->next = newnode;pos->prev = newnode;}//在pos位置之后插入节点void LTInsert(LTNode* pos, LTDataType x)//任意位置的插入{assert(pos);LTNode* newnode = LTBuyNode(x);//pos newnode pos->nextnewnode->prev = pos;newnode->next = pos->next;pos->next->prev = newnode;pos->next = newnode;}//删除pos位置的节点void LTErase(LTNode* pos){assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;}//修改void LTChange(LTNode* pos, LTDataType x){assert(pos);pos->data = x;}
(3)test.c:
#define _CRT_SECURE_NO_WARNINGS 1#include\"List.h\"void test01(){LTNode* plist = LTInit();//创建一个指针来接收初始化的返回值LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);//LTPushFront(plist, 1);//LTPushFront(plist, 2);//LTPushFront(plist, 3);//LTPushFront(plist, 4);// //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);//LTPopFront(plist);//LTPrint(plist);LTNode* pos = LTFind(plist,2);//if (pos)//{//printf(\"找到了!\\n\");//}//else//{//printf(\"未找到!\\n\");//}// //LTInsertBefore(pos, 100);//LTPrint(plist);//LTInsert(pos, 100);//LTPrint(plist);//LTErase(pos);//LTPrint(plist);//修改LTChange(pos, 100);LTPrint(plist);//优化版销毁LTDestroy(plist);plist = NULL;}int main(){test01();return 0;}

五、顺序表和链表的对比分析

详见下图——


结尾

        那么到这里,我们【初阶数据结构】中的【顺序表与链表】这一part就全部介绍完了,内容不少,大家要及时回顾、消化。

        顺序表、链表的数据结构和下面我们将要介绍的栈和队列密不可分,马虎不得。

往期回顾:

【数据结构与算法】数据结构初阶:详解顺序表和链表(四)——单链表(下)

 【数据结构与算法】数据结构初阶:详解顺序表和链表(三)——单链表(上)

本期内容需要回顾的C语言知识如下面的截图中所示(指针博主写了6篇,列出来有水字数嫌疑了,就只放指针第六篇的网址,博主在指针(六)把指针部分的前五篇的网址都放在【往期回顾】了,点击【传送门】就可以看了)。

大家如果对前面部分的知识点印象不深,可以去上一篇文章的结尾部分看看,博主把需要回顾的知识点相关的博客的链接都放在上一篇文章了,上一篇文章的链接博主放在下面了:

【数据结构与算法】数据结构初阶:详解顺序表和链表(三)——单链表(上)

结语:本篇文章到这里就结束了,对数据结构的双向链表知识感兴趣的友友们可以在评论区留言,博主创作时可能存在笔误,或者知识点不严谨的地方,大家多担待,如果大家在阅读的时候发现了行文有什么错误欢迎在评论区斧正,再次感谢友友们的关注和支持!