【数据结构初阶】--- 双链表

news2025/4/25 13:24:39

双链表你要了解的

双链表:

  • 带头双链表
  • 循环双链表
  • 带头循环双链表
    今天我们学习的,就是最强双链表 — 带头循环双链表
    下图就是带头循环双链表,所谓带头,就是有哨兵位,那么最左边的节点就是哨兵位,是不计入链表的节点数,并且不存储数据,那用来做什么呢?同学们可以提前猜想它的作用,后面我会解答的

在这里插入图片描述

带头循环双链表的特点

从三个维度分析:

  1. 带头:所谓带头就是有哨兵位,
    • 先试想,在学单链表的时候,如果这个单链表没有哨兵位,还想进行头插,此时新的结点已经成为链表的头结点,那么此时若没有将函数外的头指针的地址传入进来,那么头指针指向的还是原来的结点,所以要传头指针的地址作为实参传入进来,再通过解引用使这个头指针指向新的结点。
    • 当你现在有了哨兵位,哨兵位是个结点,类型是个结构体,将哨兵位的地址传递过来,是可以在函数内部更改这个哨兵位的指向,头插时将哨兵位中的指针指向新的结点。
    • 优点:不难看出,有了哨兵位就不再需要传递指针的地址,更不用在函数内部用二级指针接收,实在方便
  2. 循环:尾结点的next指针指向哨兵位,哨兵位的prev指针指向尾结点
    • 优点:当你想进行尾插、尾删这类操作时,不用再将链表从头遍历到尾。此时进行各类操作时,结束操作的条件,从找NULL变为了找哨兵位
  3. 双链表:一个结点存一个数据,和两个指针,分别为:指向前一个结点的指针prev,指向后面一个结点的指针next
    • 优点: 当你想对链表进行插入或删除操作时,通常需要记录前一个结点的位置,这样在实际操作中并不方便,而双链表中的指针prev就好像为解决这个而生,专门来存放前一个结点的地址
      在这里插入图片描述

双链表的结构

typedef int LTDataType;//存放数据的类型
typedef struct LTNode
{
   struct LTNode* prev;//指向前一个结点的指针
   struct LTNode* next;//指向后一个结点的指针
   LTDataType data;
}LTNode;

双链表的操作

了解了双链表的特点,也该真实体会下双链表的方便,帮我们省去了很多要分类讨论的情况

初始化

思路:
初始化的时候我们就要创建一个哨兵位,对这个哨兵位进行初始化,哨兵位的prev和next指针都指向自己,里面的data实际上用不上,可以赋值也可以不管,最后返回哨兵位的地址,函数外面实际早已经创建好一个头指针,用来存放这个哨兵位的地址

LTNode* LTInit( )
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc fail");
		return;
	}
	phead->prev = phead;
	phead->next = phead;
	phead->data = -1;

	return phead;
}

打印

思路:
遍历结束的条件与单链表不同,结束条件是遇到哨兵位

void LTPrint(LTNode* phead)
{
	assert(phead != NULL);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
}

创建一个结点

思路:
创建一个新节点,注意将里面的指针置空,避免成为野指针

LTNode* LTNodeCreat(LTDataType x)
{
	LTNode* new_node = (LTNode*)malloc(sizeof(LTNode));
	if (new_node == NULL)
	{
		perror("malloc fail");
		return;
	}
	new_node->prev = NULL;
	new_node->next = NULL;
	new_node->data = x;

	return new_node;
}

头插

思路:
申请一个临时指针指向当前的头结点,方便修改哨兵位、新节点、老头结点(插入前的头结点)的指针指向

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead != NULL);

	LTNode* nxt = phead->next;
	LTNode* new_node = LTNodeCreat(x);
	
	phead->next = new_node;
	new_node->prev = phead;
	new_node->next = nxt;
	nxt->prev = new_node;
	
}

尾插

思路:
和头插思路一样,申请一个临时指针指向尾结点,方便修改哨兵位、新节点、老的尾结点(插入前的尾结点)的指针指向

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead != NULL);

	LTNode* tail = phead->prev;
	LTNode* new_node = LTNodeCreat(x);
	
	phead->prev = new_node;
	new_node->next = phead;
	new_node->prev = tail;
	tail->next = new_node;

}

头删

思路:
要考虑只有哨兵位的时候就不能删了
申请两个指针,一个指针head指向当前的头结点,一个指针head_next指向头节点的下一个节点(即将成为新的头结点),在进行头删操作会方便些,而且代码的可读性很好
不申请也行,就是可读性不强

void LTPopFront(LTNode* phead)
{
	assert(phead != NULL);
	assert(phead->next != phead);//只有哨兵位的时候就不能删了

	LTNode* head = phead->next;
	LTNode* head_next = head->next;
	phead->next = head->next;
	head_next->prev = phead;
	
	free(head);
	head = NULL;

}

尾删

思路:
与头插的思路一致

void LTPopBack(LTNode* phead)
{
	assert(phead != NULL);
	assert(phead->next != phead);

	LTNode* tail = phead->prev;
	LTNode* tail_prev = tail->prev;
	tail_prev->next = phead;
	phead->prev = tail_prev;
	
	free(tail);
	tail = NULL;
}

查找结点并返回

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead != NULL);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

在pos之前插入

在指定位置前插入节点,插入的思路已将在前面讲过,不变,只是多了个寻找pos位置的遍历操作

void LTInsert(LTNode* phead, LTNode* pos, LTDataType x)
{
	assert(phead != NULL);
	assert(pos != NULL);

	LTNode* new_node = LTNodeCreat(x);
	LTNode* cur = phead->next;
	LTNode* cur_prev = phead;
	while (cur != phead)
	{
		if (cur == pos)
		{
			cur_prev->next = new_node;
			new_node->prev = cur_prev;
			new_node->next = cur;
			cur->prev = new_node;
		}
		cur_prev = cur;
		cur = cur->next;
	}
}

删除pos位置的值

void LTErase(LTNode* phead, LTNode* pos)
{
	assert(phead != NULL);
	assert(pos != NULL);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur = pos)
		{
			cur->prev->next = cur->next;
			cur->next->prev = cur->prev;
			free(cur);
			cur = NULL;
			return;
		}
		cur = cur->next;
	}
}

销毁双链表

注意:

哨兵位也要销毁,函数执行完虚手动将头指针置空

void LTDestory(LTNode* phead)
{
	assert(phead != NULL);
	
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* nxt = cur->next;
		free(cur);
		cur = nxt;
	}
	//哨兵位也要销毁
	free(cur);
	cur = NULL;
}

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

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

相关文章

银河麒麟系统项目部署

使用服务器信息 软件&#xff1a;VMware Workstation Pro 虚拟机&#xff1a;ubtun 内存&#xff1a;20G 虚拟机连接工具&#xff1a; MobaXterm Redis连接工具&#xff1a; RedisDesktopManager 镜像&#xff1a;F:\Kylin-Server-10-8.2-Release-Build09-20211104-X86_64…

企业都在用的镭速是如何做到高效的大文件传输的

在当前这个数字化快速发展的时代&#xff0c;文件传输速度和安全性已经成为企业运营的关键。镭速&#xff0c;作为众多企业都在用的专业大文件传输工具&#xff0c;是如何实现高效、安全的文件传输呢&#xff1f;接下来&#xff0c;我们就来深入探讨一下镭速的传输策略及其在不…

超级签名源码/超级签/ios分发/签名端本地linux服务器完成签名

该系统完全在linux下运行&#xff0c;不存在使用第三方收费工具&#xff0c;市面上很多系统都是使用的是第三方收费系统&#xff0c;例如&#xff1a;某心签名工具&#xff0c;某测侠等&#xff0c;不开源而且需要每年交费&#xff0c;这种系统只是在这些工具的基础上套了一层壳…

Superset二次开发之基于GitLab OpenAPI 查询项目的提交记录中修改的文件

背景: Superset二次开发,在处理版本升级的过程中,需要手动迁移代码, 如何在Superset项目众多的文件中,记录修改过的文件,迁移代码时只需重点关注这些文件修改的内容即可, 但是针对项目中多次的commit 信息,每个commit 又涉及不同的文件, 如何快速梳理出这些二开工作中…

C++青少年简明教程:C++的指针入门

C青少年简明教程&#xff1a;C的指针入门 说到指针&#xff0c;就不可能脱离开内存。了解C的指针对于初学者来说可能有些复杂&#xff0c;我们可以试着以一种简单、形象且易于理解的方式来解释&#xff1a; 首先&#xff0c;我们可以将计算机内存想象成一个巨大的有许多格子的…

算法金 | 再见!!!K-means

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 今天我们来聊聊达叔 6 大核心算法之 —— k-means 算法。最早由斯坦福大学的 J. B. MacQueen 于 1967 年提出&#xff0c;后来经过许多…

ASP.NET MVC企业级程序设计(查,删,展示详情,日期转换,¥字符串拼接)

题目&#xff1a; 实现过程 控制器代码 using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc;namespace MvcApplication1.Controllers {public class HomeController : Controller{//// GET: /Home/public Action…

浏览器实时播放摄像头数据并通过 Yolo 进行图像识别

安装 Ultralytics 之后&#xff0c;可以直接通过本地获取摄像头数据流&#xff0c;并通过 Yolo 模型实时进行识别。大多情况下&#xff0c;安装本地程序成本比较高&#xff0c;需要编译打包等等操作&#xff0c;如果可以直接通过浏览器显示视频&#xff0c;并实时显示识别到的对…

2024年6月-Docker配置镜像代理

步骤1&#xff1a;编辑 daemon.json 文件 vim /etc/docker/daemon.json步骤2&#xff1a;添加配置 将以下内容粘贴到文件中&#xff1a; {"insecure-registries": ["192.168.0.99:8800"],"data-root": "/mnt/docker","registr…

【Python】已解决报错:AttributeError: module ‘json‘ has no attribute ‘loads‘解决办法

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。 &#x1f913; 同时欢迎大家关注其他专栏&#xff0c;我将分享Web前后端开发、人工智能、机器学习、深…

Python实现任务进度条展示(tqdm库实现进度条)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

高考完的三个月想自学点编程,有没有什么建议

&#x1f446;点击关注 获取更多编程干货&#x1f446; 对于刚刚完成高考的学生来说&#xff0c;无论未来是否选择计算机科学作为专业方向&#xff0c;自学编程技能是一项非常有价值的投资&#xff0c;掌握编程知识能够帮助同学们为将来的学习和科研 实践奠定一个基础。 随着…

计算机网络 —— 运输层(运输层概述)

计算机网络 —— 运输层&#xff08;运输层概述&#xff09; 运输层运输层端口号复用分用复用&#xff08;Multiplexing&#xff09;分用&#xff08;Demultiplexing&#xff09; 常用端口号页面响应流程 我们今天进入到运输层的学习&#xff1a; 运输层 我们之前学习的物理层…

感受光子芯片中试线,如何点亮未来计算与通信的革命之路(2024青岛智能装备与通信技术展)

光子芯片中试线&#xff1a;点亮未来计算与通信的革命之路 在新一代信息技术的浪潮中&#xff0c;光子芯片以其低能耗、高速度的特点备受瞩目。首条光子芯片中试线的建立&#xff0c;标志着我国在光电子领域的重大突破&#xff0c;同时也为即将到来的量子计算时代奠定了坚实基…

【DivineCut 2】Blender商店10周年免费领礼物智能服装布料生成工具限时免费领取。(1个月免费使用)

Blender商店10周年免费领礼物&#xff1a;https://blendermarket.com/birthday DivineCut 2 智能服装生成工具限时免费领取。&#xff08;1个月免费使用&#xff09; &#xff08;免费测试版资源库&#xff09; DivineCut™是一款Blender工具&#xff0c;只需点击几下&#xf…

样本学习:当AI遇上“少见多怪”

东汉名臣牟融在其著作《牟子》写道&#xff1a;“少所见&#xff0c;多所怪&#xff0c;睹橐驼&#xff0c;谓马肿背。”意思是见闻少的人遇到不常见的事物就觉得奇怪&#xff0c;见到骆驼也以为是背肿了的马。因此&#xff0c;后人总用“少见多怪”来嘲笑见识浅陋的人。然而&a…

互联网创业项目,轻资产创业项目。

目录 前言&#xff1a; 一、当前有哪些热门的互联网轻资产创业项目&#xff1f; 二、这些项目是做什么的&#xff1f; 三、项目一般都是怎么做的&#xff1f; 前言&#xff1a; 当前互联网创作项目多多&#xff0c;该怎么选择合适自己的项目去做呢&#xff1f; 一、当前有哪…

Windows 文件夹(文件)备份脚本bat

使用xcopy 来实现 1、新建一个bat脚本 重命名文件为 windows_log_bak.bat 后缀也成修改为.bat 2、备份代码 xcopy参数&#xff1a; #可在命令窗口执行这个命今&#xff0c;查看所有参数详细 xcopy /? 使用的参数&#xff1a; /e&#xff1a;拷贝所有子目录&#xff0c;包括…

编译结果处理的shell脚本

#!/bin/bash WEB"web" DIST"dist" RED\033[0:31m GREEN\033[0;32m NC\033[0m #生产打包传参 BUILD"b" if [ -e ${WEB} ];then#删历史文件rm -r ${WEB}rm ${WEB}.zip fi #编辑文件 npm run build #检查构建是否成功 if[ -e ${DIST} ];then#改名mv…