探索数据结构:便捷的双向链表

🔑🔑博客主页:阿客不是客

🍓🍓系列专栏:渐入佳境之数据结构与算法

欢迎来到泊舟小课堂

😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注

​​

前言

前面我们学习了单链表,它解决了顺序表中插入删除需要挪动大量数据的缺点,使单链表解决顺序表缺陷时,我们发现作为另一种形态出现的单链表似乎也有明显的缺陷。

  1. 在部分功能实现时因为头结点的改变需要引进二级指针(或者采用返回等更为复杂的方法)导致代码更加复杂。
  2. 寻找某个节点的前一个节点,对于单链表而言只能遍历,这样就可能造成大量时间的浪费。
  3. 尾部以及指定位置插入、删除数据的时间复杂度为O(N) ,效率低下。

为了解决这个问题,我们就要学习今天的主角——双向链表

一、带头双向循环链表的介绍

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构,虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构无头单向不循环链表,带头双向循环链表。

对比单链表来了解,带头双向循环链表:结构复杂,一般单独存储数据。凭着其复杂的结构我们可以做到快速管理数据,实现对数据的操作。

带头双向循环链表具有以下特点:

  1. 头节点:带头双向循环链表包含一个头节点,它位于链表的起始位置,并且不存储实际数据。头节点的前指针指向尾节点,头节点的后指针指向第一个实际数据节点。
  2. 循环连接:尾节点的后指针指向头节点,而头节点的前指针指向尾节点,将链表形成一个循环连接的闭环。这样可以使链表在遍历时可以无限循环,方便实现循环操作。
  3. 双向连接:每个节点都有一个前指针和一个后指针,使得节点可以向前和向后遍历。前驱指针指向前一个节点,后继指针指向后一个节点。

总结:带头双向循环链表可以支持在链表的任意位置进行插入和删除操作,并且可以实现正向和反向的循环遍历。通过循环连接的特性,链表可以在连续的循环中遍历所有节点,使得链表的操作更加灵活和高效。

二、带头双向循环双链表的实现

2.1 创建链表

双向链表的定义结构体需要包含三个成员,一个成员存储数值,一个成员存储前一个节点的地址,最后一个成员存储下一个节点的地址。

typedef int LTDataType;
typedef struct ListNode
{LTDataType val;struct ListNode* next;struct ListNode* prev;
}LTN;

2.2 初始化链表

在初始化双向链表时,我们需要创建一个头节点,也就是我们常说的哨兵位头节点

这里需要注意一下:创建头结点的时候,因为链表中没有其它的数据,我们在初始化的时候让guard的next和prev都要指向自己,这样才是一个循环链表(如下图所示 ↓↓↓ )

LTN* LTinit()
{LTN* phead = CreateLTNode(-1);phead->next = phead;phead->prev = phead;return phead;
}

2.3 创建新结点

跟之前的一样就不过多介绍了

LTN* CreateLTNode(LTDataType x)
{LTN* newnode = (LTN*)malloc(sizeof(LTN));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}

2.4 打印链表

这个链表中的结点是没有NULL的,因此在判断循环是否要结束的条件应该是判断tail是否等于phead

void LTprint(LTN* phead)
{assert(phead);printf("哨兵位 <--> ");LTN* tail = phead->next;while (tail != phead){printf("%d <--> ",tail->val);tail = tail->next;}printf("哨兵位\n");
}

2.5 双向链表的查找

和单链表一样,我们也可以对双向链表进行查找。如果找到就返回该节点的地址,否则返回NULL。

LTN* LTFind(LTN* phead, LTDataType x)
{assert(phead);LTN* cur = phead->next;while (cur != phead){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;
}

2.6 双向链表的插入

2.5.1 尾插

单链表尾插结点需要遍历全链表,当指针走到链表最后一个结点的时候,判断tail->next是否为NULL,若为NULL,则跳出遍历的循环,尾插新结点。然而带头双向循环链表不需要遍历链表,只需要对哨兵位的头节点的prev域解引用,直接找到带头双向循环链表的尾节点,尾插新节点。

头指针的区别:带头双向循环链表不需要判断头指针是否指向NULL,因为哨兵位的头节点也是有它的地址的,添加新节点时只需要直接在尾节点尾插。然而单链表却需要判断头指针是否指向NULL,而且需要用到二级指针,比较棘手。

 

void LTpushback(LTN* phead, LTDataType x)
{assert(phead);//带哨兵位头结点,只改变了结构体成员,不需要二级指针LTN* tail = phead->prev;LTN* newnode = CreateLTNode(x);tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}

2.5.2 头插

void LTpushfront(LTN* phead, LTDataType x)
{assert(phead);LTN* tail = phead->next;LTN* newnode = CreateLTNode(x);tail->prev = newnode;newnode->next = tail;newnode->prev = phead;phead->next = newnode;
}

2.5.3 任意位置插入

void LTInsert(LTN* pos, LTDataType x)
{assert(pos);LTN* newnode = CreateLTNode(x);LTN* tail = pos->prev;pos->prev = newnode;newnode->next = pos;newnode->prev = tail;tail->next = newnode;
}

和单链表不同,双向链表头尾操作完全可以用任意位置操作替代。 

LTInsert实现尾插:
LTInsert(phead, x);
LTInsert实现头插:
LTInsert(phead->next, x);

2.7 双向链表的删除

2.6.1 尾删

 当链表只有一个节点(哨兵位不算)时:

若链表为NULL(只剩哨兵位就是链表为NULL)时,再尾删就被assert会出错:

void LTpopback(LTN* phead)
{assert(phead);assert(phead->next != phead);LTN* tail = phead->prev;LTN* tailprev = tail->prev;tailprev->next = phead;phead->prev = tailprev;free(tail);tail = NULL;
}

2.6.2 头删

链表不止一个结点时:

链表为一个结点时:

代码示例如下: 

void LTpopfront(LTN* phead)
{assert(phead);assert(phead->next != phead);LTN* tail = phead->next;LTN* tailnext = tail->next;tailnext->prev = phead;phead->next = tailnext;free(tail);tail = NULL;
}

2.6.3 任意位置删除

void LTErase(LTN* pos)
{assert(pos);LTN* tailnext = pos->next;LTN* tailprev = pos->prev;tailnext->prev = tailprev;tailprev->next = tailnext;free(pos);pos = NULL;
}
LTErase实现尾删:
LTErase(phead->prev);

 LTErase实现头删:

LTErase(phead->next);

2.8 销毁链表

void LTDestroy(LTN* phead)
{assert(phead);LTN* cur = phead->next;while (cur != phead){LTN* next = cur->next;free(cur);cur = next;}free(phead);
} 

三、完整代码

3.1 LTN.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{LTDataType val;struct ListNode* next;struct ListNode* prev;
}LTN;LTN* LTinit();//初始化链表void LTprint(LTN* phead);void LTpushback(LTN* phead, LTDataType x);
void LTpushfront(LTN* phead, LTDataType x);
void LTpopback(LTN* phead);
void LTpopfront(LTN* phead);LTN* LTFind(LTN* phead, LTDataType x);void LTInsert(LTN* pos, LTDataType x);
void LTErase(LTN* pos);

3.2 LTN.c

#include"SLTN.h"LTN* CreateLTNode(LTDataType x)
{LTN* newnode = (LTN*)malloc(sizeof(LTN));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}LTN* LTinit()
{LTN* phead = CreateLTNode(-1);phead->next = phead;phead->prev = phead;return phead;
}void LTprint(LTN* phead)
{assert(phead);printf("哨兵位 <--> ");LTN* tail = phead->next;while (tail != phead){printf("%d <--> ",tail->val);tail = tail->next;}printf("哨兵位\n");
}void LTpushback(LTN* phead, LTDataType x)
{assert(phead);//带哨兵位头结点,只改变了结构体成员,不需要二级指针LTN* tail = phead->prev;LTN* newnode = CreateLTNode(x);tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;//LTInsert(phead->prev, x);
}void LTpushfront(LTN* phead, LTDataType x)
{assert(phead);LTN* tail = phead->next;LTN* newnode = CreateLTNode(x);tail->prev = newnode;newnode->next = tail;newnode->prev = phead;phead->next = newnode;//LTInsert(phead->next, x);
}void LTpopback(LTN* phead)
{assert(phead);assert(phead->next != phead);LTN* tail = phead->prev;LTN* tailprev = tail->prev;tailprev->next = phead;phead->prev = tailprev;free(tail);tail = NULL;//LTErase(phead->prev);
}void LTpopfront(LTN* phead)
{assert(phead);assert(phead->next != phead);LTN* tail = phead->next;LTN* tailnext = tail->next;tailnext->prev = phead;phead->next = tailnext;free(tail);tail = NULL;//LTErase(phead->next);
}LTN* LTFind(LTN* phead, LTDataType x)
{assert(phead);LTN* cur = phead->next;while (cur != phead){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;
}void LTInsert(LTN* pos, LTDataType x)
{assert(pos);LTN* newnode = CreateLTNode(x);LTN* tail = pos->prev;pos->prev = newnode;newnode->next = pos;newnode->prev = tail;tail->next = newnode;
}void LTErase(LTN* pos)
{assert(pos);LTN* tailnext = pos->next;LTN* tailprev = pos->prev;tailnext->prev = tailprev;tailprev->next = tailnext;free(pos);pos = NULL;
}

四、顺序表与链表优缺点分析

  • 链表(双向)优势:
  1. 任意位置插入删除都是O(1)
  2. 按需申请释放,合理利用空间,不存在浪费
  • 问题:
  1. 下标随机访问不方便
  • 顺序表问题:
  1. 头部或中间插入删除效率低,要挪动数据O(N)
  2. 空间不够要扩容,扩容有一定消耗,且可能存在一定的空间浪费
  3. 只适合尾插尾删
  • 优势: 
  1. 支持下标随机访问O(1),可以进行排序操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/21483.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ICLR24大模型提示(3/11) | PromptAgent:利用语言模型进行战略规划,实现专家级提示优化

【摘要】高效的、针对特定任务的提示通常由专家精心设计&#xff0c;以整合详细的说明和领域见解&#xff0c;这些见解基于对大型语言模型 (LLM) 的本能和目标任务的复杂性的深刻理解。然而&#xff0c;自动生成这种专家级提示仍然难以实现。现有的提示优化方法往往忽视领域知识…

20240603每日AI------------项目引入Spring Cloud Alibaba AI (二)

项目源码解析 前端代码&#xff1a; <div class"container"><h1>Spring Cloud Alibaba AI Example</h1><form id"form"><label for"message">User Message&#xff1a;</label><input type"text&q…

大模型PEFT(一)之推理实践学习记录

1. 简介 多种模型: LLaMA、Mistral、Mixtral-MoE、Qwen、Yi、Gemmha、Baichuan、ChatGLM、Phi等等。集成方法:(增量)预训练、指令监督微调、奖励模型训练、PPO训练和DPO训练。多种精度:32比特全参数微调、16比特冻结微调、16比特LORA微调和基于AQLM/AWQ/GPTQ/LLM.int8 的2/4/8…

操作字符串获取文件名字(包含类型)

记录一种操作字符串获取文件名字的操作方式&#xff0c;方便后期的使用。示例&#xff1a; 输入&#xff1a;"D:/code/Test/Test.txt" 输出&#xff1a;"Test.txt" 设计思路&#xff1a; 首先查找路径中最后一个”/“&#xff0c;然后再通过字符串截取的…

湖南源点调研 为什么中小企业产品上市前一定要做市场调研?

本文由湖南长沙&#xff08;产品前测&#xff09;源点调研咨询编辑发布 可能有很多企业主会表示&#xff0c;市场调研&#xff0c;产品调研&#xff0c;不都是大公司、大品牌、上市公司才会有的流程吗&#xff0c;像我们这种小企业、小品牌、小厂家没有必要去那么做&#xff0…

开源VS闭源:大模型发展路径之争,你站哪一派?

文章目录 引言一、数据隐私1.1开源大模型的数据隐私1.2 闭源大模型的数据隐私1.3 综合考量 二、商业应用2.1 开源大模型的商业应用2.2 闭源大模型的商业应用2.3 商业应用的综合考量 三、社区参与3.1 开源大模型的社区参与3.2 闭源大模型的社区参与3.3 综合考量 结论 引言 在人…

解析“分层引流”在颅内感染治疗中的价值意义

临床中&#xff0c;化脓性颅内感染的治疗一直是界内关注的重点。近年来&#xff0c;得益于医疗技术的持续革新与提升&#xff0c;颅内感染的治疗方法也获得了不断的更新与优化。在此背景下&#xff0c;北京精诚博爱医院所倡导的“分层引流”理念&#xff0c;作为一种新兴的治疗…

什么牌子的开放式耳机质量好?2024超强实力派品牌推荐!

耳机对于一个音乐人有重要这个不必多说&#xff0c;我朋友是个音乐编辑&#xff0c;他经常需要长时间佩戴耳机进行音频编辑和混音工作。在尝试过多款开放式耳机后&#xff0c;都没找到合适的。今天&#xff0c;我将从专业角度为大家带来几款热门开放式耳机的测评报告&#xff0…

第二证券炒股知识:股票内盘外盘代表什么意思?

股票内盘是主动性卖盘&#xff0c;表明以买入价成交的股数&#xff0c;持股的投资者主动以等于或是低于买一、买二、买三、买四、买五的价格卖出手中持有的股份&#xff0c;买入成交数量核算参加内盘。 股票外盘是主动性买盘&#xff0c;表明以卖出价成交的股数&#xff0c;场…

跟着大佬学RE(一)

学了一个 map&#xff08;&#xff09;函数的使用 import base64rawData "e3nifIH9b_CndH" target list(map(ord, rawData)) # map 函数将 rawData 中的每个字符传递给 ord 函数。ord 函数返回给定字符的 Unicode 码点 print(target) # 打印 map 对象的内存地址&…

电脑中病毒了怎么办?7招教你保护电脑安全!

“不知道怎么回事&#xff0c;我的电脑莫名其妙就中病毒了&#xff0c;实在不知道应该怎么操作了&#xff0c;希望大家可以帮我&#xff01;” 在数字化时代的浪潮中&#xff0c;电脑已成为我们生活与工作中不可或缺的一部分。然而&#xff0c;就像任何事物都有其阴暗面一样&am…

ip地址快速切换软件有哪些好处

ip地址快速切换软件有哪些好处&#xff1f;IP地址快速切换软件具有诸多显著的好处&#xff0c;以下是对其主要优势的详细阐述&#xff1a; 首先&#xff0c;IP地址快速切换软件极大地提升了网络活动的灵活性和便捷性。对于需要经常切换网络环境或进行多账号管理的用户而言&…

AI大模型在穿戴设备健康中的心率深度融合与案例分析

文章目录 1. 架构设计2. 应用场景3. 实现步骤3.1 步骤1&#xff1a;数据预处理3.2 步骤2&#xff1a;边缘计算初步分析3.3 步骤3&#xff1a;数据上传到云端3.4 步骤4&#xff1a;云端复杂分析3.5 步骤5&#xff1a;深度学习模型训练与部署 4. 云端API设计4.1 安装Flask4.2 API…

美国前总统特朗普竟然入驻TikTok,粉丝破24万

大家好&#xff01; 我是老洪&#xff01; 刚看到一则关于美国前总统特朗普的新闻&#xff0c; 特朗普竟然入驻TikTok了&#xff0c;太令人惊讶了。&#xff08;为什么惊讶&#xff0c;后面再说&#xff09; 更为惊人的是&#xff0c;他的到来竟然引来了众多粉丝的热烈追捧&…

如何从清空的回收站中恢复已删除的Word文档?

“嗨&#xff0c;我将 10 个 Word 文档移动到回收站&#xff0c;然后用清洁软件清理回收站。现在我意识到我犯了一个大错误——我删除了错误的文件。我想知道是否可以从清空的回收站中恢复已删除的Word文档。我没有数据恢复的经验&#xff0c;也不精通计算机技术。有没有简单的…

三.一布局和布局切换的实践与探索

在前端开发中&#xff0c;灵活的布局切换是一项非常实用的功能。今天&#xff0c;我想和大家分享一下如何在主组件中通过更换 Layout 目录下的组件来实现布局切换。 首先&#xff0c;我们有一个主组件 index.vue&#xff0c;它承担着整个页面的主要逻辑和展示。 而在 Layout …

SG90舵机(Arduino)/XY轴摇杆使用(Arduino)

XY轴摇杆使用 需要注意&#xff0c;必须是 ADC 引脚才可以接收模拟信号输入 /** 接线* PS2摇杆 Arduino* x A0* y A1* sw 7 */const int yg_x_pin A0; const int yg_y_pin A1; const int yg_btn_pin 7; // 摇杆按下的输入引脚 void…

旋转编码器、DS1302 实时时钟、红外遥控模块、雨滴探测传感器 | 配合Arduino使用案例

旋转编码器 旋转编码器是一种用作检测自动化领域中的角度、速度、长度、位置和加速度的传感器。 有绝对式和增量式&#xff0c;这里使用增量式&#xff08;相对&#xff09;。 绝对输出只是周的当前位置&#xff0c;是他们成为角度传感器。增量输出关于轴的运动信息&#xff0…

Python中degrees怎么用

degrees() 函数可以将弧度转换为角度。 语法 以下是 degrees() 方法的语法&#xff1a; import math math.degrees(x) 注意&#xff1a;degrees() 是不能直接访问的&#xff0c;需要导入 math 模块&#xff0c;然后通过 math 静态对象调用该方法。 参数 x -- 一个数值。 返…

视频SK配置教程

视频SK配置教程 提供的pika接口服务&#xff08;国外的&#xff0c;所以要反代&#xff09;&#xff0c;创建一个pika账号并开通pika套餐 反向配置教程 https://blog.csdn.net/u012241616/article/details/139391954?spm1001.2014.3001.5502 1、进入站点后台->功能->…