【数据结构】双向奔赴的爱恋 --- 双向链表

在这里插入图片描述
关注小庄 顿顿解馋๑ᵒᯅᵒ๑

引言:上回我们讲解了单链表(单向不循环不带头链表),我们可以发现他是存在一定缺陷的,比如尾删的时候需要遍历一遍链表,这会大大降低我们的性能,再比如对于链表中的一个结点我们是无法直接访问它的上一个结点,那有什么解决方法呢?这里就得请出我们今天的主角----双链表。

文章目录

  • 一. 🏠 什么是双链表
  • 二. 🏠 双链表的实现
    • 👿 双链表结点
    • 👿 双链表哨兵位的创建
    • 👿 双链表插入数据
    • 👿 双链表删除数据
    • 👿 双链表查找
    • 👿 pos结点前插入数据和删除pos结点数据
    • 👿 双链表打印和销毁
  • 三. 🏠 双链表的分析

一. 🏠 什么是双链表

在这里我们讲的双链表有三个特点 :双向 , 循环 , 带头 。我们分别理解这三个特点~

  • 双向 循环
    在这里插入图片描述
    优势:1.每一个结点都能很方便访问它的后一个结点和前一个结点 2.方便找到尾节点,提高了效率。

  • 带头
    在这里插入图片描述
    图中的head就是哨兵位

  1. 这里的带头跟我们之前所说的头节点有所不同,这里的带头,不存储有效数据起到一个哨兵的作用。
  2. 哨兵位的作用:遍历循环链表避免死循环,其次涉及到头节点的删除和插入时,无需考虑NULL的问题。

双链表的这三个特点将会使得实现它比实现单链表更简单~


二. 🏠 双链表的实现

👿 双链表结点

为了能循环和双向,我们双链表的一个结点需要两个指针。

typedef int Datatype;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* pre;
	Datatype x;
}ListNode;

👿 双链表哨兵位的创建

ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (NULL == newnode)
	{
		perror("malloc failed");
		return;
	}
	newnode->x = x;
	newnode->next = newnode;
	newnode->pre = newnode;

1.注意next指针和pre指针都要指向自己。
2.由于插入数据也要创建新结点,所以我们可以直接创建一个申请结点的接口方便复用。

//申请新结点的接口
ListNode* BuyNode(Datatype x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (NULL == newnode)
	{
		perror("malloc failed");
		return;
	}
	newnode->x = x;
	newnode->next = newnode;
	newnode->pre = newnode;
	return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* phead = BuyNode(-1); //哨兵位
	return phead;
}

👿 双链表插入数据

  • 尾插
    双链表的尾插指的是将新节点插入到哨兵位之前
    在这里插入图片描述

1.黄色箭头和蓝色箭头是我们要修改的指针指向
2.注意:要先改变蓝色箭头的对应关系,如果先让head的pre变成newnode话,后边newnode->pre = plist就会指向自己
3.小技巧:不管三七二十一,插入直接先改newnode的next和pre

// 双向链表尾插  尾插是插到plist的前面
void ListPushBack(ListNode* plist, Datatype x)
{
	assert(plist);
	ListNode* newnode = BuyNode(x);
	newnode->next = plist;
	newnode->pre = plist->pre;
	plist->pre->next = newnode;
	plist->pre = newnode;
}
  • 头插
    在这里插入图片描述
// 双向链表头插 头插是插到哨兵位的后面
void ListPushFront(ListNode* plist, Datatype x)
{
	ListNode* newnode = BuyNode(x);
	ListNode* del = plist->next;
	newnode->next = del;
	newnode->pre = plist;
	del->pre = newnode;
	plist->next = newnode;
}

*是不是很easy,跟单链表比起来 ~ *

👿 双链表删除数据

  • 尾删
    在这里插入图片描述
    对于尾删 只需要改它前面一个结点next和哨兵位的pre就好了,存好pre结点的位置
void ListPopBack(ListNode* plist)
{
	assert(plist);
	assert(plist->next != plist);
	ListNode* ptail = plist->pre;
	ListNode* pre = ptail->pre;
	pre->next = plist;
	plist->pre = pre;
	free(ptail);
	ptail = NULL;
}
  • 头删

在这里插入图片描述

// 双向链表头删
void ListPopFront(ListNode* plist)
{
	assert(plist);
	assert(plist->next != plist);
	ListNode* pNext = plist->next->next;
	ListNode* pcur = plist->next;
	plist->next = pNext;
	pNext->pre = plist;
	free(pcur);
	pcur = NULL;
}

👿 双链表查找

遍历链表找到就停下,如果没找到循环到head停止,返回NULL。大大提现了哨兵位的好处

// 双向链表查找
ListNode* ListFind(ListNode* plist, Datatype x)
{
	assert(plist);
	ListNode* pcur = plist->next;
	while (pcur != plist)
	{
		if (pcur->x == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

👿 pos结点前插入数据和删除pos结点数据

类似尾插尾删,头插头删,改变指针指向即可

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, Datatype x)
{
	assert(pos);
	ListNode* newnode = BuyNode(x);
	ListNode* pre = pos->pre;
	newnode->next = pos;
	newnode->pre = pre;
	pre->next = newnode;
	pos->pre = newnode;
}
// 双向链表删除pos位置的结点
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* pre = pos->pre;
	ListNode* pNext = pos->next;
	pre->next = pNext;
	pNext->pre = pre;
	free(pos);
	pos = NULL;
}

👿 双链表打印和销毁

循环遍历到phead停止~

// 双向链表打印
void ListPrint(ListNode* plist)
{
	assert(plist);
	ListNode* pcur = plist->next;
	while (pcur != plist)
	{
		printf("%d->", pcur->x);
		pcur = pcur->next;
	}
	printf("\n");
}
// 双向链表销毁
void ListDestory(ListNode* plist)
{
	ListNode* pcur = plist->next;
	while (pcur != plist)
	{
		ListNode* del = pcur->next;
		free(pcur);
		pcur = del;
	}
	free(pcur);
	pcur = NULL; //无效
}

注意:由于函数形参是实参的一份临时拷贝,所以要在函数外手动置空!


三. 🏠 双链表的分析

经过如上我们实现的双链表结构,我们不禁发现它比单链表功能的强大,那它是否是完美的呢?答案是否的,没有完美的人,也没有完美的数据结构。

优点:
1.双链表单次任意位置插入和删除效率较高,比单链表还要效率高
2.双链表不存在空间浪费,按需申请和释放空间
3.双链表的一个结点可以访问前后结点(相比于单链表)
缺点:
1.和单链表一样,虽然双链表访问尾结点快,但是任然不支持随机访问
2.cpu高速缓存命中率低,因为结点地址可能是分散的。


本次双链表的讲解就到此结束啦,各位看官能否与我双向奔赴来个三连呢! ! !

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【探究图论中dfs记忆化,搜索,递推,回溯关系】跳棋,奶牛隔间, 小A和uim之大逃离 II

本篇很高能,如有错误欢迎指出,本人能力有限(需要前置知识记忆化dfs,树形dp,bfsdp,tarjan) 另外,本篇之所以属于图论,也是想让各位明白,dfs就是就是在跑图&am…

DNS 服务 Unbound 部署最佳实践

文章目录 安装unbound-control配置启动服务测试 参考: 官网地址:https://nlnetlabs.nl/projects/unbound/about/ 详细文档:https://unbound.docs.nlnetlabs.nl/en/latest/index.html DNS服务Unbound部署于使用 https://cloud.tencent.com/…

cryptography,一个神奇的 Python 库!

更多资料获取 📚 个人网站:ipengtao.com 大家好,今天为大家分享一个神奇的 Python 库 - cryptography。 Github地址:https://github.com/pyca/cryptography 在当今数字化时代,信息安全越来越受到重视。数据加密是保护…

海外媒体发稿:9种高效的媒体套餐内容发稿策略分析-华媒舍

海外媒体发稿:9种高效的媒体套餐内容发稿策略分析高效的媒体发布和营销推广策略对公司、本人的成就尤为重要。下面我们就对于媒体套餐内容发稿营销推广策略开展全面解析,帮助读者掌握并应用这9种合理的思路,进而获得更好的媒体营销效果。 1.媒…

基于react native的自定义轮播图

基于react native的自定义轮播图 效果示例图示例代码 效果示例图 示例代码 import React, {useEffect, useRef, useState} from react; import {Animated,PanResponder,StyleSheet,Text,View,Dimensions, } from react-native; import {pxToPd} from ../../common/js/device;c…

小目标检测篇 | YOLOv8改进之GSConv + Slim Neck提升小目标检测效果

前言:Hello大家好,我是小哥谈。在文章中,作者提出了一种新方法GSConv来减轻模型的复杂度并保持准确性。GSConv可以更好地平衡模型的准确性和速度。并且,提供了一种设计范式Slim Neck,以实现检测器更高的计算成本效益。实验过程中,与原始网络相比,改进方法获得了最优秀的…

GDAl 之绘制栅格图像的大致直方图和精准直方图(8)

gdal的绘制大致直方图是仅查看概览或者抽样像素的一个子集 import os from osgeo import gdal import matplotlib.pyplot as plt import numpy as np# Dont forget to change directory. os.chdir(rD:\DeskTop\learn_py_must\Learn_GDAL\osgeopy-data\osgeopy-data\Switzerlan…

基于Selenium+Python的web自动化测试框架!

简介: 本文将详细介绍如何运用Python结合Selenium WebDriver库搭建web自动化测试框架。 一、什么是Selenium? Selenium是一个基于浏览器的自动化测试工具,它提供了一种跨平台、跨浏览器的端到端的web自动化解决方案。Selenium主要包括三部分…

音视频处理 - 音频概念详解,码率,采样率,位深度,声道,编码

1. 音频采样 与视频不同,音频的最小单位不是一帧,而是一个采样。 采样是当前一刻声音的声音样本,样本需要经过数字转换才能存储为样本数据。 真实声音是连续的,但是在计算机中,声音是离散且均匀的声音样本。 2. 位深…

ER图与关系模型

1.设某商业集团数据库中有三个实体集。 “公司”实体集,属性有公司编号、公司名、地址等; “仓库”实体集,属性有仓库编号、仓库名、地址等; “职工”实体集,属性有职工编号、姓名、性别等。公司与仓库间存在“隶属…

《被讨厌的勇气》读书思考笔记 (好书推荐)

《被讨厌的勇气》是一本由日本心理学家岸见一郎和奥冈昌高合著的畅销心理成长书籍。这本书基于心理学家阿尔弗雷德阿德勒的思想,介绍了“自我决定心理学”的观点,探讨了人们如何克服害怕失败,勇敢追求自己真正想要的生活。这本书在心理学、自…

HCIP的学习(4)

GRE和MGRE VPN---虚拟专用网络。指依靠ISP(运营商)或其他公有网络基础设施上构建的专用的安全数据通信网络。该网络是属于逻辑上的。​ 核心机制—隧道机制(封装技术) GRE—通用路由封装 ​ 三层隧道技术,并且是属于…

Git基础(23):Git分支合并实战保姆式流程

文章目录 前言准备正常分支合并1. 创建两个不冲突分支2. 将dev合并到test 冲突分支合并1. 制造分支冲突2. 冲突合并 前言 Git分支合并操作 准备 这里先在Gitee创建了一个空仓库,方便远程查看内容。 正常分支合并 1. 创建两个不冲突分支 (1&#xf…

Tableau项目案例-网上超市运营分析

一、数据简要介绍 超市运营分析.xlsx 1、客户分析 交易次数统计 购买次数即购买频率,是指消费者在一定时期内购买某种或某类商品的次数。 用tableau打开excel文件 双击城市字段,会显出出一个地图 类别字段也拖到筛选器上,如上操作相同

AI论文速读 | 具有时间动态的路网语义增强表示学习

论文标题: Semantic-Enhanced Representation Learning for Road Networks with Temporal Dynamics 作者: Yile Chen(陈亦乐) ; Xiucheng Li(李修成); Gao Cong(丛高) ; Zhifeng Ba…

管理能力学习笔记四:团队发展四阶段

组建期 管理方式 动荡期 领导方式 规范期 管理方式 高产期 管理方式 高产期的注意点

FL Studio21.2.3最新中文编曲音乐制作软件新版本功能介绍

一、前言 随着科技的发展,越来越多的人开始尝试自己创作音乐。然而,传统的音乐制作过程复杂繁琐,需要昂贵的硬件设备和专业的知识技能。那么,有没有一款软件可以让普通人也能轻松地制作出专业级别的音乐作品呢?答案就…

什么是 ECMAScript,它与 JavaScript 有何不同

什么是 ECMAScript? 关于 JavaScript](https://cloudaffle.com/history-of-javascript/)的[历史以及它是如何产生的,有一个完整的故事。长话短说,ECMAScript 中的 ECMA 是指欧洲计算机制造商协会,早在 1997 年就向该协会提交了 JavaScript 1.1 进行标准化。创建了一个技术委员…

机器学习(二)

线性模型: 离散转为连续的变换: 检查是否有“序”的变化,若有“序”,则连续化;否则,转化为k维向量 最小二乘解: 多元线性回归: 广义线性模型: 线性判别分析: 由于将样例投影到一条直线(低维空间),因此也被视为一种&q…

洛谷1803

P1803 凌乱的yyy / 线段覆盖 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 所需知识:贪心 本来还想用dfs bfs搜索来一点一点做的,看到了大佬的思路之后,直接orz了 整体思路:因为要想尽可能的多参加比赛,所以越早结…