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

数据结构初阶:详解双链表

🔥个人主页:胡萝卜3.0

🎬作者简介:C++研发方向学习者

📖个人专栏:  《C语言》《数据结构》 《C++干货分享》

⭐️人生格言:不试试怎么知道自己行不行

目录

一、链表的分类

1.1 带头或者不带头

1.2 单向或者双向

1.3 循环或者不循环

二、双向链表

2.1 概念

2.2 结构

三、双向链表的实现

3.1 双向链表的结构

3.2 双向链表的初始化

3.3 打印

3.4 判断链表是否为空

3.5 增删查改功能的实现

3.5.1 尾插

3.5.2 头插

3.5.3 尾删

3.5.4 头删

3.5.5 查找

3.5.6 在指定位置之前插入数据

3.5.6 在指定位置之后插入数据

3.5.7 删除指定位置上的数据

3.5.8 删除指定位置之前的数据

3.5.9 删除指定位置之后的数据

3.5.10 修改指定位置上的数据

3.5.11 销毁链表

四、完整代码

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

5.1 初始化优化

5.2 销毁优化

5.3 优化后完整代码

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


一、链表的分类

链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:

1.1 带头或者不带头

带头链表中的头结点,不存储任何有效数据,只是用来占位子,我们称它为“哨兵位”

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

1.2 单向或者双向

1.3 循环或者不循环

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

ok,接下来我们一起来学习双链表的相关知识。

二、双向链表

2.1 概念

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

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

2.2 结构

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

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

当我们没有向链表中插入任何数据之前,这个链表是一个空链表,那双链表的空链表和单链表的空链表是一样的吗?

ok,当然是不一样的,双向链表是带头双向循环链表,如果仅仅是一个空节点,那就不满足这个概念了,那双向链表为空怎么表示呢?

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

三、双向链表的实现

3.1 双向链表的结构

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

//双链表的结构
typedef int LTNDataType;
typedef struct ListNode
{LTNDataType data;//存储数据struct ListNode* prev;//指针保存上一个节点的地址struct ListNode* next;//指针保存的是下一个结点的地址
}ListNode;
3.2 双向链表的初始化

这时候就会有人想问了,在学习单链表时,我们并没有实现初始化的操作,为什么到了双链表,要实现初始化操作呢?

在双向链表中的头结点实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这里“放哨的”,双向链表为空——指向自己,那我们是不是要给这个头结点向操作系统申请空间,并在申请空间时,让prev和next指针指向自己,这样才可以实现双向循环。

代码

(1)List.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//双链表的结构
typedef int LTNDataType;
typedef struct ListNode
{LTNDataType data;//存储数据struct ListNode* prev;//指针保存上一个节点的地址struct ListNode* next;//指针保存的是下一个结点的地址
}ListNode;//要改变头节点plist——要用二级指针
//初始化
void ListInit(ListNode** pphead);

(2)List.c

//初始化
void ListInit(ListNode** pphead)
{ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));if (tmp == NULL){perror("malloc fail!");exit(1);}*pphead = tmp;(*pphead)->data = -1;(*pphead)->prev = (*pphead)->next = *pphead;
}

(3)test.c

#define  _CRT_SECURE_NO_WARNINGS  1#include"List.h"void test01()
{ListNode* plist = NULL;ListInit(&plist);
}int main()
{test01();return 0;
}
3.3 打印

要实现打印的操作,那我们就要遍历链表:

代码:

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

pcur为头结点的下一个结点,当pcur!=phead时,继续遍历链表,当pcur==phead时,跳出循环,停止遍历。

3.4 判断链表是否为空

当双链表为空时,仅仅只有一个头结点,那就是头结点中的next指针指向头结点。

代码

//判断链表是否为空
bool ListEmpty(ListNode* phead)
{assert(phead);//头结点的next指针指向自己,说明链表为空return phead->next == phead;
}
3.5 增删查改功能的实现
3.5.1 尾插

在尾插的操作中,哨兵位没有发生改变。

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

代码:
List.c

//创建空间
ListNode* ListBuyNode(LTNDataType num)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = num;newnode->prev = newnode->next = newnode;return newnode;
}
//尾插
void ListPushBack(ListNode* phead, LTNDataType num)
{assert(phead);//为新节点创建空间ListNode* newnode = ListBuyNode(num);//先改变newnode的指向newnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}

test.c

void test01()
{ListNode* plist = NULL;ListInit(&plist);//尾插ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPushBack(plist, 5);ListPushBack(plist, 6);ListPrint(plist);
}
int main()
{test01();return 0;
}

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

3.5.2 头插

List.c

//创建空间
ListNode* ListBuyNode(LTNDataType num)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = num;newnode->prev = newnode->next = newnode;return newnode;
}
//头插
//在头结点的后面插入
void ListPushFront(ListNode* phead, LTNDataType num)
{assert(phead);//为新结点创建空间ListNode* newnode = ListBuyNode(num);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
void test01()
{ListNode* plist = NULL;ListInit(&plist);ListNode* plist = ListInit();//尾插ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPushBack(plist, 5);ListPushBack(plist, 6);ListPrint(plist);//头插ListPushFront(plist, 1);ListPushFront(plist, 0);ListPrint(plist);
}
int main()
{test01();return 0;
}

时间复杂度为:O(1)

3.5.3 尾删

List.c

//判断链表是否为空
bool ListEmpty(ListNode* phead)
{assert(phead);//头结点自己指向自己,说明链表为空return phead->next == phead;
}
//尾删
void ListPopBack(ListNode* phead)
{//判断双链表是否为空assert(!ListEmpty(phead));ListNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
3.5.4 头删

头删删除的是头结点后面的一个结点,而不是头结点

List.c

//判断链表是否为空
bool ListEmpty(ListNode* phead)
{assert(phead);//头结点自己指向自己,说明链表为空return phead->next == phead;
}//头删
void ListPopFront(ListNode* phead)
{//判断链表是否为空assert(!ListEmpty(phead));ListNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}
3.5.5 查找

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

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

List.c

//查找
ListNode* ListCheckNode(ListNode* phead, LTNDataType num)
{assert(!ListEmpty(phead));ListNode* pcur = phead->next;while (pcur != phead){if (pcur->data == num){return pcur;}pcur = pcur->next;}return NULL;
}
3.5.6 在指定位置之前插入数据

List.c

//在指定位置之前插入数据
void ListInsertFront(ListNode* pos, LTNDataType num)
{assert(pos);//为新节点创建空间ListNode* newonde = ListBuyNode(num);newonde->next = pos;newonde->prev = pos->prev;pos->prev->next = newonde;pos->prev = newonde;
}
3.5.6 在指定位置之后插入数据

List.c

//在指定位置之后插入数据
void ListInsertBack(ListNode* pos, LTNDataType num)
{assert(pos);//为新节点创建空间ListNode* newnode = ListBuyNode(num);newnode->prev = pos;newnode->next = pos->next;pos->next->prev = newnode;pos->next = newnode;
}
3.5.7 删除指定位置上的数据

List.c

//删除指定位置上的数据
void ListErase(ListNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}
3.5.8 删除指定位置之前的数据

List.c

//删除指定位置之前的数据
void ListEraseFront(ListNode* pos)
{assert(pos);ListNode* del = pos->prev;del->prev->next = pos;pos->prev = del->prev;free(del);del = NULL;
}
3.5.9 删除指定位置之后的数据

List.c

//删除指定位置之后的数据
void ListEraseBack(ListNode* pos)
{assert(pos);ListNode* del = pos->next;del->next->prev = pos;pos->next = del->next;free(del);del = NULL;
}
3.5.10 修改指定位置上的数据
//修改指定位置上的数据
void ListChangeNode(ListNode* pos, LTNDataType num)
{assert(pos);pos->data = num;
}
3.5.11 销毁链表

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

List.c

//销毁链表
void ListDestory(ListNode** pphead)
{ListNode* pcur = (*pphead)->next;while (pcur != *pphead){ListNode* next = pcur->next;free(pcur);pcur = next;}free(*pphead);*pphead = NULL;
}

四、完整代码

List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//双链表的结构
typedef int LTNDataType;
typedef struct ListNode
{LTNDataType data;//存储数据struct ListNode* prev;//指针保存上一个节点的地址struct ListNode* next;//指针保存的是下一个结点的地址
}ListNode;
//打印
void ListPrint(ListNode* phead);
//要改变头节点plist——要用二级指针
//初始化
void ListInit(ListNode** pphead);
//ListNode* ListInit();
//尾插
void ListPushBack(ListNode* phead, LTNDataType num);
//头插
void ListPushFront(ListNode* phead, LTNDataType num);
//尾删
void ListPopBack(ListNode* phead);
//头删
void ListPopFront(ListNode* phead);
//查找
ListNode* ListCheckNode(ListNode* phead, LTNDataType num);
//在指定位置之后插入数据
void ListInsertBack(ListNode* pos, LTNDataType num);
//在指定位置之前插入数据
void ListInsertFront(ListNode* pos, LTNDataType num);
//删除指定位置上的数据
void ListErase(ListNode* pos);
//删除指定位置之前的数据
void ListEraseFront(ListNode* pos);
//删除指定位置之后的数据
void ListEraseBack(ListNode* pos);
//修改指定位置上的数据
void ListChangeNode(ListNode* pos, LTNDataType num);
//判断链表是否为空
bool ListEmpty(ListNode* phead);
//销毁链表
void ListDestory(ListNode** pphead);

List.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
//创建空间
ListNode* ListBuyNode(LTNDataType num)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = num;newnode->prev = newnode->next = newnode;return newnode;
}
//初始化
void ListInit(ListNode** pphead)
{ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));if (tmp == NULL){perror("malloc fail!");exit(1);}*pphead = tmp;(*pphead)->data = -1;(*pphead)->prev = (*pphead)->next = *pphead;
}
//打印
void ListPrint(ListNode* phead)
{assert(phead);ListNode* pcur = phead->next;while (pcur != phead){printf("%d -> ", pcur->data);pcur = pcur->next;}printf("\n");
}
//判断链表是否为空
bool ListEmpty(ListNode* phead)
{assert(phead);//头结点自己指向自己,说明链表为空return phead->next == phead;
}
//尾插
void ListPushBack(ListNode* phead, LTNDataType num)
{assert(phead);//为新节点创建空间ListNode* newnode = ListBuyNode(num);//先改变newnode的指向newnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}
//头插
//在头结点的后面插入
void ListPushFront(ListNode* phead, LTNDataType num)
{assert(phead);//为新结点创建空间ListNode* newnode = ListBuyNode(num);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}
//尾删
void ListPopBack(ListNode* phead)
{//判断双链表是否为空assert(!ListEmpty(phead));ListNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
//头删
void ListPopFront(ListNode* phead)
{assert(!ListEmpty(phead));ListNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}
//查找
ListNode* ListCheckNode(ListNode* phead, LTNDataType num)
{assert(!ListEmpty(phead));ListNode* pcur = phead->next;while (pcur != phead){if (pcur->data == num){return pcur;}pcur = pcur->next;}return NULL;
}
//在指定位置之后插入数据
void ListInsertBack(ListNode* pos, LTNDataType num)
{assert(pos);//为新节点创建空间ListNode* newnode = ListBuyNode(num);newnode->prev = pos;newnode->next = pos->next;pos->next->prev = newnode;pos->next = newnode;
}
//在指定位置之前插入数据
void ListInsertFront(ListNode* pos, LTNDataType num)
{assert(pos);//为新节点创建空间ListNode* newonde = ListBuyNode(num);newonde->next = pos;newonde->prev = pos->prev;pos->prev->next = newonde;pos->prev = newonde;
}
//删除指定位置上的数据
void ListErase(ListNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}
//删除指定位置之前的数据
void ListEraseFront(ListNode* pos)
{assert(pos);ListNode* del = pos->prev;del->prev->next = pos;pos->prev = del->prev;free(del);del = NULL;
}
//删除指定位置之后的数据
void ListEraseBack(ListNode* pos)
{assert(pos);ListNode* del = pos->next;del->next->prev = pos;pos->next = del->next;free(del);del = NULL;
}
//修改指定位置上的数据
void ListChangeNode(ListNode* pos, LTNDataType num)
{assert(pos);pos->data = num;
}
//销毁链表
void ListDestory(ListNode** pphead)
{ListNode* pcur = (*pphead)->next;while (pcur != *pphead){ListNode* next = pcur->next;free(pcur);pcur = next;}free(*pphead);*pphead = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
void test01()
{ListNode* plist = NULL;ListInit(&plist);//尾插ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPushBack(plist, 5);ListPushBack(plist, 6);ListPrint(plist);//头插ListPushFront(plist, 1);ListPushFront(plist, 0);ListPrint(plist);//尾删ListPopBack(plist);ListPrint(plist);//头删ListPopFront(plist);ListPrint(plist);//查找ListNode* pos = ListCheckNode(plist, 5);//在指定位置后面插入ListInsertBack(pos, 6);ListPrint(plist);//在指定位置之前插入pos = ListCheckNode(plist, 1);ListInsertFront(pos, 0);ListPrint(plist);//删除指定位置上的数据ListErase(pos);ListPrint(plist);//删除指定位置之前的数据pos = ListCheckNode(plist, 4);ListEraseFront(pos);ListPrint(plist);//删除指定位置之后的数据ListEraseBack(pos);ListPrint(plist);ListEraseBack(pos);ListPrint(plist);//销毁链表ListDestory(&plist);
}
int main()
{test01();return 0;
}

ok,写到这里我们就完成了双链表的代码书写,但是我们发现上面的代码中,除了初始化和销毁的代码,其余的函数声明中都是一级指针,所以我们需要考虑接口的一致性。

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

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

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

5.1 初始化优化

List.c

ListNode* ListInit(LTNDataType num)
{ListNode* phead = ListBuyNode(-1);return phead;
}

test.c

#define  _CRT_SECURE_NO_WARNINGS  1#include"List.h"void test01()
{/*ListNode* plist = NULL;ListInit(&plist);*/ListNode* plist = ListInit(-1);
}int main()
{test01();return 0;
}
5.2 销毁优化

List.c

void ListDestory(ListNode* phead)
{ListNode* pcur = phead->next;while (pcur != phead){ListNode* next = pcur->next;free(pcur);pcur = next;}
}

test..c

void test01()
{//销毁链表ListDestory(plist);plist = NULL;
}
int main()
{test01();return 0;
}

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

5.3 优化后完整代码

List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//双链表的结构
typedef int LTNDataType;
typedef struct ListNode
{LTNDataType data;//存储数据struct ListNode* prev;//指针保存上一个节点的地址struct ListNode* next;//指针保存的是下一个结点的地址
}ListNode;
//打印
void ListPrint(ListNode* phead);
//初始化
ListNode* ListInit(LTNDataType num);
//尾插
void ListPushBack(ListNode* phead, LTNDataType num);
//头插
void ListPushFront(ListNode* phead, LTNDataType num);
//尾删
void ListPopBack(ListNode* phead);
//头删
void ListPopFront(ListNode* phead);
//查找
ListNode* ListCheckNode(ListNode* phead, LTNDataType num);
//在指定位置之后插入数据
void ListInsertBack(ListNode* pos, LTNDataType num);
//在指定位置之前插入数据
void ListInsertFront(ListNode* pos, LTNDataType num);
//删除指定位置上的数据
void ListErase(ListNode* pos);
//删除指定位置之前的数据
void ListEraseFront(ListNode* pos);
//删除指定位置之后的数据
void ListEraseBack(ListNode* pos);
//修改指定位置上的数据
void ListChangeNode(ListNode* pos, LTNDataType num);
//判断链表是否为空
bool ListEmpty(ListNode* phead);
//销毁链表
void ListDestory(ListNode* phead);

List.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
//创建空间
ListNode* ListBuyNode(LTNDataType num)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = num;newnode->prev = newnode->next = newnode;return newnode;
}
//初始化
ListNode* ListInit(LTNDataType num)
{ListNode* phead = ListBuyNode(-1);return phead;
}
//打印
void ListPrint(ListNode* phead)
{assert(phead);ListNode* pcur = phead->next;while (pcur != phead){printf("%d -> ", pcur->data);pcur = pcur->next;}printf("\n");
}
//判断链表是否为空
bool ListEmpty(ListNode* phead)
{assert(phead);//头结点自己指向自己,说明链表为空return phead->next == phead;
}
//尾插
void ListPushBack(ListNode* phead, LTNDataType num)
{assert(phead);//为新节点创建空间ListNode* newnode = ListBuyNode(num);//先改变newnode的指向newnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}
//头插
//在头结点的后面插入
void ListPushFront(ListNode* phead, LTNDataType num)
{assert(phead);//为新结点创建空间ListNode* newnode = ListBuyNode(num);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}
//尾删
void ListPopBack(ListNode* phead)
{//判断双链表是否为空assert(!ListEmpty(phead));ListNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
//头删
void ListPopFront(ListNode* phead)
{assert(!ListEmpty(phead));ListNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}
//查找
ListNode* ListCheckNode(ListNode* phead, LTNDataType num)
{assert(!ListEmpty(phead));ListNode* pcur = phead->next;while (pcur != phead){if (pcur->data == num){return pcur;}pcur = pcur->next;}return NULL;
}
//在指定位置之后插入数据
void ListInsertBack(ListNode* pos, LTNDataType num)
{assert(pos);//为新节点创建空间ListNode* newnode = ListBuyNode(num);newnode->prev = pos;newnode->next = pos->next;pos->next->prev = newnode;pos->next = newnode;
}
//在指定位置之前插入数据
void ListInsertFront(ListNode* pos, LTNDataType num)
{assert(pos);//为新节点创建空间ListNode* newonde = ListBuyNode(num);newonde->next = pos;newonde->prev = pos->prev;pos->prev->next = newonde;pos->prev = newonde;
}
//删除指定位置上的数据
void ListErase(ListNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}
//删除指定位置之前的数据
void ListEraseFront(ListNode* pos)
{assert(pos);ListNode* del = pos->prev;del->prev->next = pos;pos->prev = del->prev;free(del);del = NULL;
}
//删除指定位置之后的数据
void ListEraseBack(ListNode* pos)
{assert(pos);ListNode* del = pos->next;del->next->prev = pos;pos->next = del->next;free(del);del = NULL;
}
//修改指定位置上的数据
void ListChangeNode(ListNode* pos, LTNDataType num)
{assert(pos);pos->data = num;
}
//销毁链表
void ListDestory(ListNode* phead)
{ListNode* pcur = phead->next;while (pcur != phead){ListNode* next = pcur->next;free(pcur);pcur = next;}
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
void test01()
{ListNode* plist = ListInit(-1);//尾插ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPushBack(plist, 5);ListPushBack(plist, 6);ListPrint(plist);//头插ListPushFront(plist, 1);ListPushFront(plist, 0);ListPrint(plist);//尾删ListPopBack(plist);ListPrint(plist);//头删ListPopFront(plist);ListPrint(plist);//查找ListNode* pos = ListCheckNode(plist, 5);//在指定位置后面插入ListInsertBack(pos, 6);ListPrint(plist);//在指定位置之前插入pos = ListCheckNode(plist, 1);ListInsertFront(pos, 0);ListPrint(plist);//删除指定位置上的数据ListErase(pos);ListPrint(plist);//删除指定位置之前的数据pos = ListCheckNode(plist, 4);ListEraseFront(pos);ListPrint(plist);//删除指定位置之后的数据ListEraseBack(pos);ListPrint(plist);ListEraseBack(pos);ListPrint(plist);//销毁链表ListDestory(plist);plist = NULL;
}
int main()
{test01();return 0;
}

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

如果在今后需要进行数据的任意插入和删除,使用链表;若需要大量的存储数据和访问,使用顺序表

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

相关文章:

  • 在angular20中使用monaco-editor
  • LLM Agent工具设计规范总结
  • Python 实现归并排序:从原理到代码
  • Ansys Electronics Desktop 2025 R2 软件界面介绍
  • open webui源码分析10-四个特征之搜索
  • 系统架构师-软件工程
  • 前端响应式设计
  • 推荐系统王树森(五)重排多样性提升
  • 银河麒麟桌面操作系统:为什么不让root直接登录图形界面?以及如何安全地解决这个问题
  • 英伟达营收和利润均超出市场预期,但盘后一度跌超5%
  • Groovy语法入门
  • [光学原理与应用-321]:皮秒深紫外激光器产品不同阶段使用的工具软件、对应的输出文件
  • 客户案例 | 国际知名内衣品牌x甄知科技,领航IT服务新征程
  • linux 网络:并发服务器及IO多路复用
  • 开发避坑指南(42):Vue3 element ui el-table单选实现方案
  • HTML静态页面实现材积计算器和材积表生成器
  • 动手学深度学习(pytorch版):第七章节—现代卷积神经网络(5)批量规范化
  • 吴炳锡:AI 时代下的湖仓一体化平台建设的思考
  • 从枯燥C++到趣味音乐:我的Windows系统底层探索之旅
  • 公共字段自动填充
  • Foundry工具大全
  • uniapp vue3 canvas实现手写签名
  • 【Node.js从 0 到 1:入门实战与项目驱动】2.4 开发工具推荐(VS Code 、IDEA及插件、终端工具、调试工具)
  • Cesium入门教程(二)环境搭建(HTML版)
  • QT:【第一个QT程序】【信号和槽】
  • 使用uni-app和Canvas生成简易双列表格布局并长按下载到本地
  • 混淆 打包 编译
  • Ovis2.5技术解密:原生分辨率与“反思模式”如何铸就新一代MLLM王者
  • 2024年山东省信息学小学组(CSP-X)第一轮题解
  • 冒泡排序算法详解(python code)