数据结构——双向链表

devtools/2024/11/15 4:06:36/

目录

一、链表的分类

 (1)单向或双向​编辑

(2)带头或不带头​编辑

(3)循环或不循环​编辑

 (4)补充

二、实现双向链表

(1)List.h

(2)List.c

(3)注意

三、顺序表和链表的比较

 四、写在最后


一、链表的分类

链表的结构多种多样,包括:带头或不带头、单向或双向、循环或不循环,组合起来有8种情况(2x2x2):

 (1)单向或双向

(2)带头或不带头

(3)循环或不循环

 (4)补充

①虽然链表有多种结构,但是最常用的是单链表(不带头单向不循环链表)、双向链表(带头双向循环链表);

②带头指的是头结点,这里的头结点和我们之前说的是两个概念,实际上在前面的称呼并不严谨。头结点实际为“哨兵位”,哨兵位不存储有效数据,作用为“放哨的”;

③双向链表的结构

二、实现双向链表

(1)List.h

#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 LTInit1(LTNode** pphead);
LTNode* LTInit2();//打印
void LTPrint(LTNode* phead);//插入
void LTPushFront(LTNode* phead, LTDatatype x);
void LTPushBack(LTNode* phead, LTDatatype x);//判断链表是否为空
bool LTEmpty(LTNode* phead);//删除
void LTPopFront(LTNode* phead);
void LTPopBack(LTNode* phead);//查找数据位置
LTNode* LTFind(LTNode* phead, LTDatatype x);//在指定位置之后插入数据
void LTInit(LTNode* pos, LTDatatype x);//删除指定位置的数据
void LTErase(LTNode* pos);//销毁
void LTDestroy(LTNode** pphead);
void LTDestroy2(LTNode* phead);//需要手动将plist置为空

(2)List.c

#include "List.h"LTNode* LTBuyNode(LTDatatype x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//如果申请不成功if (newnode == NULL){perror("malloc fail!\n");exit(1);//退出}newnode->data = x;newnode->next = newnode->prev = newnode;//由于双向链表是循环的,所以指向自身而非NULLreturn newnode;//注意返回新结点
}//初始化
void LTInit1(LTNode** pphead)
{//创建哨兵位*pphead = LTBuyNode(-1);
}LTNode* LTInit2()
{LTNode* phead = LTBuyNode(-1);return phead;
}//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}//插入
void LTPushFront(LTNode* phead, LTDatatype x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}void LTPushBack(LTNode* phead, LTDatatype x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}//判断链表是否为空
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}//删除
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));//判断链表不为空LTNode* del = phead->next;phead->next = del->next;del->next->prev = phead;free(del);del = NULL;
}
void LTPopBack(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));//判断链表不为空LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
//查找数据位置
LTNode* LTFind(LTNode* phead, LTDatatype x)
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//在指定位置之后插入数据
void LTInit(LTNode* pos, LTDatatype x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}//删除指定位置的数据
void LTErase(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}//销毁
void LTDestroy(LTNode** pphead)
{assert(pphead && *pphead);LTNode* pcur = (*pphead)->next;while (pcur != *pphead){LTNode* Next = pcur->next;free(pcur);pcur = Next;}free(*pphead);*pphead = NULL;pcur = NULL;//记得将pcur置为空,否则如果后续使用,它为野指针
}void LTDestroy2(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* Next = pcur->next;free(pcur);pcur = Next;}free(phead);phead = NULL;pcur = NULL;
}

(3)注意

1.双向链表是循环的,在创建新结点时,新结点的next、prev指针要指向自身。因为如果链表为空,此时链表只有头节点,要想构成循环,其next、prev指针必须指向自身;

2.除初始化和销毁的函数传入的是二级指针外,其他函数的形参均为一级指针,为了保持接口的一致性,给它们传入一级指针作为优化。但美中不足的是,对于链表的销毁,还需在调用函数后将实参置为空;

3.插入时需要创建新结点,删除时需要判断链表是否为空。

三、顺序表和链表的比较

不同点顺序表链表(单链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持,复杂度为O(1)不支持,复杂度为O(N)
任意位置插入或删除元素可能需要搬运元素,效率低,复杂度为O(N)只需要修改指针的指向
插入时的空间动态顺序表,空间不够时需要扩容,还可能造成空间浪费没有容量的概念,按需申请和释放,不存在空间浪费的情况
应用场景高效存储元素+频繁访问

任意位置高效插入和删除

 四、写在最后

我们的链表终于完结啦,撒花~~

敬请期待栈和队列!


http://www.ppmy.cn/devtools/86439.html

相关文章

UPLOAD-LABS靶场[超详细通关教程,通关攻略]

---------------------------------------- 靶场环境&#xff1a; 下载链接&#xff1a; https://codeload.github.com/c0ny1/upload-labs/zip/refs/heads/master 使用小皮集成环境来完成这个靶场 将文件放到WWW目录下就可以进行访问 ------------------------------------…

Java面试八股之简述spring boot的目录结构

简述spring boot的目录结构 Spring Boot 项目遵循标准的 Maven 或 Gradle 项目布局&#xff0c;并且有一些约定的目录用于组织不同的项目组件。下面是一个典型的 Spring Boot 项目目录结构&#xff1a; src/main/java&#xff1a;包含所有的 Java 源代码&#xff0c;通常按包组…

新手必看:Elasticsearch 入门全指南

Elasticsearch 入门介绍 Elasticsearch 是一个开源的分布式搜索和分析引擎&#xff0c;广泛应用于处理大规模数据和实时搜索需求。它基于 Apache Lucene 构建&#xff0c;具备高可扩展性和分布式特性&#xff0c;能够快速、可靠地存储、搜索和分析大量数据。本文将介绍 Elasti…

使用Safari中的WebDriver进行测试

启用WebDriver并运行测试。 概述 本文将指导您完成设置WebDriver和运行用Python编写的测试的过程。当测试调用一个命令时&#xff0c;该命令按照以下步骤执行&#xff1a; 客户端库将每个命令翻译成REST API命令。REST API命令向Safari的驱动程序托管的本地web服务器发送相应…

leetcode日记(51)不同路径Ⅱ

和上一道题&#xff08;无障碍物的最短路径&#xff09;很像&#xff0c;但事实上比上一题多了优化方法 根据上一题改的代码如下&#xff0c;添加了对障碍物的判定&#xff0c;如果有障碍物则将数组值设为0。 class Solution { public:int uniquePathsWithObstacles(vector&l…

golang websocket 手写研究机制

// 处理ws请求 func WsHandler(w http.ResponseWriter, r *http.Request, id string) {var conn *websocket.Connvar err errorpingTicker : time.NewTicker(time.Second * 10)conn, err wsupgrader.Upgrade(w, r, nil)if err ! nil {log.Printf("Failed to set websocke…

CANoe在使用时碰到的一些很少见的Bug

CANoe作为一款成熟且稳定的总线仿真与测试工具&#xff0c;深受汽车工程师们的喜爱。CANoe虽然稳定&#xff0c;但作为一个软件来说&#xff0c;在使用中总会出现一些或大或小的Bug。最近全球范围内的大规模蓝屏事件&#xff0c;是由某个安全软件引起的。而很多CANoe使用者最近…

钉钉小程序内部跳转

1.跳转小程序携带参数 //触发跳转事件的方法 handleJump() {dd.navigateToMiniProgram({// 需要跳转的小程序的appIdappId: XXXXXXXXXXXXXXX,//需要跳转的小程序的页面path: pages/func/camera/camera,//参数extraData: {isOutSide: "1"},success: (res) > {cons…