数据结构——双向链表的实现

一、双向链表的结构

注意:双向链表又称带头双向循环链表
这⾥的“带头”跟前⾯我们说的“头节点”是两个概念,实际前⾯的在单链表阶段称呼不严
谨,但是为了同学们更好的理解就直接称为单链表的头节点。
带头链表⾥的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这⾥“放哨 的”
哨兵位”存在的意义: 遍历循环链表避免死循环。
双向链表每个节点储存一个有效数据+前驱指针+后继指针

二、. 双向链表的实现

2.1 创建&初始化

2.2.1  List.h

#pragma once
typedef struct ListNode
{int val;struct ListNode* next;struct  ListNode* prev;}LTNode; //初始化
LTNode* LTInit();

2.2.2  List.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
LTNode* LTInit()//哨兵位初始化
{LTNode* head = (LTNode*)malloc(sizeof(LTNode));head->val = -1;head->next = head->prev =head;return head;
}

2.2.3 text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{LTNode* head;head=LTInit();return 0;
}

代码运行测试:

2.2尾插&头插

分析:

尾插
1.往d3节点的后面插入数据叫做尾插  

 2.往哨兵位head之前插入数据也叫尾插 

 

头插

在哨兵位和头节点之间插入

2.2.1  List.h

//尾插
//1.往d3节点的后面插入数据叫做尾插    2.往哨兵位head之前插入数据也叫尾插
void LTPushBack(LTNode* head, int x);//打印
void LTPrint(LTNode* head);
//头插
void LTPushFront(LTNode* head, int x);

2.2.2  List.c

//创建新节点
LTNode* Listbuynode(int x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));node->val = x;node->next = node->prev = NULL;return node;
}
void LTPushBack(LTNode* head, int x)
{LTNode* node = Listbuynode(x);//对新节点进行操作node->next = head;node->prev = head->prev;//对原来的尾节点和哨兵位进行操作head->prev->next = node;head->prev = node;
}
void LTPrint(LTNode* head)
{assert(head);LTNode* pcur = head->next;while (pcur != head){printf("%d->", pcur->val);pcur = pcur->next;}printf("\n");
}void LTPushFront(LTNode* head, int x)
{LTNode* node = Listbuynode(x);//对新节点进行操作node->next = head->next;node->prev = head;//对哨兵位和头节点进行操作head->next->prev = node;head->next = node;
}

2.2.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{LTNode* head;head=LTInit();LTPushBack(head, 1);LTPushBack(head, 2);LTPushBack(head, 3);LTPushFront(head,4);//4->1->2->3LTPrint(head);return 0;
}

2.3  头删&尾删

2.3.1  List.h

//尾删
void LTPopBack(LTNode* head);//头删
void LTPopFront(LTNode* head);

2.3.2  List.c

void LTPopBack(LTNode* head)
{//链表为空不能删除assert(head);assert(head->next != head);//将尾节点进行保存LTNode* del = head->prev;//连接次尾节点和哨兵位del->prev->next = head;head->prev = del->prev;free(del);del = NULL;}
void LTPopFront(LTNode* head)
{//链表为空不能删除assert(head);assert(head->next != head);//将头节点进行保存LTNode* del = head->next;//连接哨兵位和次头节点head->next = del->next;del->next->prev = head;free(del);del = NULL;
}

2.3.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{LTNode* head;head=LTInit();LTPushBack(head, 1);LTPushBack(head, 2);LTPushBack(head, 3);LTPushFront(head, 4);LTPrint(head);//4->1->2->3LTPopFront(head);LTPrint(head);//1->2->3LTPopBack(head);LTPrint(head);1->2return 0;
}

2.4  查找数据&在pos节点后插入数据&删除pos节点

2.4.1  List.h


//在pos位置之后插入数据
void LTInsert(LTNode* pos, int x);
//删除pos节点
void LTErase(LTNode* pos);//查找数据
LTNode* LTFind(LTNode* head, int x);

2.4.2  List.c

void LTInsert(LTNode* pos, int x)
{assert(pos);LTNode* node = Listbuynode(x);//先处理新节点node->prev = pos;node->next = pos->next;//在处理前后节点pos->next = node;node->next->prev = node;
}LTNode* LTFind(LTNode* head, int x)
{assert(head);assert(head->next!=head);LTNode* pcur = head->next;while (pcur != head){if (pcur->val == x){return pcur;}pcur = pcur->next;}return NULL;
}
void LTErase(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}

2.4.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{LTNode* head;head=LTInit();LTPushBack(head, 1);LTPushBack(head, 2);LTPushBack(head, 3);LTPushBack(head, 4);//LTPopBack(head);//LTPrint(head);//LTPopBack(head);LTNode* find = LTFind(head, 4);LTInsert(find, 11);LTPrint(head);//1->2->3->4->11LTErase(find);//1->2->3->11LTPrint(head);return 0;
}

2.5  销毁

若销毁接口用二级指针接受,传哨兵位指针的地址,那么可以改变哨兵位(指针指向),使哨兵位指向NULL;

若销毁接口用一级指针接受,传一级指针(哨兵位指针),传过去的形参(是指针存储的地址),不能够改变指针的指向,在对形参操作,可以释放哨兵位指向的地址空间(形参的值为空间地址),但是不能改变实参指针的指向(实参依然指向原来被释放的地址空间),需要手动将实参置为NULL.

简而言之,若需要改变一级指针指向,需要传二级指针。

前面都是用一级指针传参,为了保持接口的一致性,我们用一级指针传参

2.5.1  List.h

//销毁
void LTDestroy(LTNode* phead);

2.5.2  List.c

void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = pcur->next;while (pcur != phead){free(pcur);pcur = next;next = next->next;}free(phead);phead = NULL;
}

2.5.3  text.c 

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{LTNode* head;head=LTInit();LTPushBack(head, 1);LTPushBack(head, 2);LTPushBack(head, 3);LTPushFront(head, 4);/*LTPrint(head);LTPopFront(head);*/LTPrint(head);//LTPopBack(head);//LTPrint(head);//LTPopBack(head);LTNode* find = LTFind(head, 4);/*LTInsert(find, 11);*/LTErase(find);LTPrint(head);LTDestroy(head);head = NULL;return 0;
}

2.6  完整代码

2.6.1  List.h

#pragma once
typedef struct ListNode
{int val;struct ListNode* next;struct  ListNode* prev;}LTNode; //初始化
LTNode* LTInit();
//销毁
void LTDestroy(LTNode* phead);
//尾插
//1.往d3节点的后面插入数据叫做尾插    2.往哨兵位head之前插入数据也叫尾插
void LTPushBack(LTNode* head, int x);//打印
void LTPrint(LTNode* head);
//头插
void LTPushFront(LTNode* head, int x);//尾删
void LTPopBack(LTNode* head);//头删
void LTPopFront(LTNode* head);//在pos位置之后插入数据
void LTInsert(LTNode* pos, int x);
//删除pos节点
void LTErase(LTNode* pos);//查找数据
LTNode* LTFind(LTNode* head, int x);

2.6.2  List.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
LTNode* LTInit()//哨兵位初始化
{LTNode* head = (LTNode*)malloc(sizeof(LTNode));head->val = -1;head->next = head->prev =head;return head;
}
//创建新节点
LTNode* Listbuynode(int x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));node->val = x;node->next = node->prev = NULL;return node;
}
void LTPushBack(LTNode* head, int x)
{LTNode* node = Listbuynode(x);//对新节点进行操作node->next = head;node->prev = head->prev;//对原来的尾节点和哨兵位进行操作head->prev->next = node;head->prev = node;
}
void LTPrint(LTNode* head)
{assert(head);LTNode* pcur = head->next;while (pcur != head){printf("%d->", pcur->val);pcur = pcur->next;}printf("\n");
}void LTPushFront(LTNode* head, int x)
{LTNode* node = Listbuynode(x);//对新节点进行操作node->next = head->next;node->prev = head;//对哨兵位和头节点进行操作head->next->prev = node;head->next = node;
}
void LTPopBack(LTNode* head)
{//链表为空不能删除assert(head);assert(head->next != head);//将尾节点进行保存LTNode* del = head->prev;//连接次尾节点和哨兵位del->prev->next = head;head->prev = del->prev;free(del);del = NULL;}
void LTPopFront(LTNode* head)
{//链表为空不能删除assert(head);assert(head->next != head);//将头节点进行保存LTNode* del = head->next;//连接哨兵位和次头节点head->next = del->next;del->next->prev = head;free(del);del = NULL;
}void LTInsert(LTNode* pos, int x)
{assert(pos);LTNode* node = Listbuynode(x);//先处理新节点node->prev = pos;node->next = pos->next;//在处理前后节点pos->next = node;node->next->prev = node;
}LTNode* LTFind(LTNode* head, int x)
{assert(head);assert(head->next!=head);LTNode* pcur = head->next;while (pcur != head){if (pcur->val == x){return pcur;}pcur = pcur->next;}return NULL;
}
void LTErase(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = pcur->next;while (pcur != phead){free(pcur);pcur = next;next = next->next;}free(phead);phead = NULL;
}

2.6.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{LTNode* head;head=LTInit();LTPushBack(head, 1);LTPushBack(head, 2);LTPushBack(head, 3);LTPushFront(head, 4);/*LTPrint(head);LTPopFront(head);*/LTPrint(head);//LTPopBack(head);//LTPrint(head);//LTPopBack(head);LTNode* find = LTFind(head, 4);/*LTInsert(find, 11);*/LTErase(find);LTPrint(head);LTDestroy(head);head = NULL;return 0;
}

三、顺序表和双向链表的优缺点分析

不同点
顺序表
链表(单链表)
存储空间上
物理上一定连续
逻辑上连续,但物理上不⼀定连续
随机访问
⽀持O(1)
不支持,O(n)
任意位置插⼊或者删除元素
可能需要搬移元素,效率低O(N)
只需修改指针指向
插入
动态顺序表,空间不够时需要扩容
没有容量的概念
应⽤场景
元素⾼效存储+频繁访问
任意位置插⼊和删除频繁

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

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

相关文章

轻量封装WebGPU渲染系统示例<8>- 渲染器基本场景管理(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/main/src/voxgpu/sample/RSceneTest.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 2. 高频调用与低频调用隔离。 3. 面向用户的易用性封装。 4. 渲染数据和渲染机制分离。 5. 用户操作和渲…

浅谈js代码的封装方法(2023.10.30)

常见的js代码封装方法 2023.10.30 需求1、js代码封装的优缺点2、js代码封装方式2.1 方式一&#xff1a;function function declarations2.1.1 示例 2.2 方式二&#xff1a;class2.2.1 class declarations2.2.2 Class expressions 2.3 变量函数2.4 变量闭包匿名函数2.5 闭包函数…

随机链表的复制(C++解法)

题目 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节…

基于SpringBoot的垃圾分类管理系统

基于SpringBootVue的垃圾分类管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven主要功能&#xff1a;包括前台和后台两部分、首页列表展示、垃圾分类、垃圾图谱、查看详…

【详细教程】关于如何使用GitGitHub的基本操作汇总GitHub的密钥配置 ->(个人学习记录笔记)

文章目录 1. Git使用篇1.1 下载安装Git1.2 使用Git 2. GitHub使用篇2.1 如何git与GitHub建立联系呢&#xff1f;2.2 配置公钥 1. Git使用篇 1.1 下载安装Git 点击 官网链接 后&#xff0c;进入Git官网&#xff0c;下载安装包 然后根据系统类型进行下载&#xff0c;一般为wind…

Linux 音频驱动实验

目录 音频接口简介为何需要音频编解码芯片&#xff1f;WM8960 简介I2S 总线接口I.MX6ULL SAI 简介 硬件原理图分析音频驱动使能修改设备树使能内核的WM8960 驱动alsa-lib 移植alsa-utils 移植 声卡设置与测试amixer 使用方法音乐播放测试MIC 录音测试LINE IN 录音测试 开机自动…

第16期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练 Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…

Ceph入门到精通-bluestore IO流程及导入导出

bluestore 直接管理裸设备&#xff0c;实现在用户态下使用linux aio直接对裸设备进行I/O操作 写IO流程&#xff1a; 一个I/O在bluestore里经历了多个线程和队列才最终完成&#xff0c;对于非WAL的写&#xff0c;比如对齐写、写到新的blob里等&#xff0c;I/O先写到块设备上&am…

【操作系统】考研真题攻克与重点知识点剖析 - 第 1 篇:操作系统概述

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

总结之数据分析工具cube.js通过Docker部署

cube.js介绍 官网地址&#xff1a;https://cube.dev/ Cube.js是一个开源的模块化框架&#xff0c;用于构建分析web应用程序。它主要用于构建内部业务智能工具或向现有应用程序添加面向客户的分析。 Cube.js设计用于无服务器查询引擎&#xff0c;如AWS Athena和谷歌BigQuery。…

《HelloGitHub》第 91 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 Python、…

BI是什么?想要了解BI需要从哪些方面入手?

企业为了执行数字化战略&#xff0c;实行数字化转型&#xff0c;实现数据价值&#xff0c;除了需要相关数字化技术及理念、人才等&#xff0c;还需要借助数字化相关应用&#xff0c;例如商业世界中广受企业欢迎的ERP、OA、CRM等业务信息系统&#xff0c;以及上升势头非常迅猛的…

京东科技埋点数据治理和平台建设实践 | 京东云技术团队

导读 本文核心内容聚焦为什么要埋点治理、埋点治理的方法论和实践、奇点一站式埋点管理平台的建设和创新功能。读者可以从全局角度深入了解埋点、埋点治理的整体思路和实践方法&#xff0c;落地的埋点工具和创新功能都有较高的实用参考价值。遵循埋点治理的方法论&#xff0c;…

nodejs+vue学生考勤综合平台的设计与实现-计算机毕业设计

在当今高度发达的信息中&#xff0c;信息管理改革已成为一种更加广泛和全面的趋势。 “学生考勤综合平台”是基于Mysql数据库&#xff0c;在 程序设计的基础上实现的。为确保中国经济的持续发展&#xff0c;信息时代日益更新&#xff0c;蓬勃发展。 因此&#xff0c;国内外技术…

mybatis-plus正确使用姿势:依赖配置、Mapper扫描、多数据源、自动填充、逻辑删除。。。

一、前言 本文基于 springboot、maven、jdk1.8、mysql 开发&#xff0c;所以开始前我们需要准备好这套环境。 1.1 依赖准备 想要什么依赖版本的去 maven 仓库查看&#xff1a;https://mvnrepository.com/ 引入 mybatis-plus 依赖&#xff1a; <dependency><group…

1 — NLP 的文本预处理技术

一、说明 在本文中&#xff0c;我们将讨论以下主题&#xff1a;1为什么文本预处理很重要&#xff1f;2 文本预处理技术。这个文对预处理做一个完整化、程序化处理&#xff0c;这对NLP处理项目中有很大参考性。 二、为什么文本预处理很重要&#xff1f; 数据质量显着影响机器学习…

【C++项目】高并发内存池第五讲内存回收释放过程介绍

内存回收 1.ThreadCache2.CentralCache3.PageCache 项目源代码&#xff1a;高并发内存池 1.ThreadCache void ThreadCache::Deallocate(void* ptr, size_t size) {assert(ptr);assert(size < MAX_BYTES);//计算在哪号桶中&#xff0c;然后插入进去size_t index SizeClass…

Docker 笔记(上篇)

Docker 概述 Docker 概念 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux或Windows操作系统的机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之…

qml之ui控件

文章目录 ui控件移动版风格嵌套页面并排界面 ui控件 Qt Quick控件用于创建由标准化组件&#xff08;如按钮、标签、滑块等&#xff09;构建的用户界面。 QtQuick.Controls&#xff1a;基本控件。QtQuick.Templates&#xff1a;为控件提供行为化的、非可化视的基本类型。QtQui…

基于旗鱼算法的无人机航迹规划-附代码

基于旗鱼算法的无人机航迹规划 文章目录 基于旗鱼算法的无人机航迹规划1.旗鱼搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用旗鱼算法来优化无人机航迹规划。 1.旗鱼搜索算法 …