双链表(带哨兵位头节点)

news2024/9/28 22:31:50

目录

​编辑

 双链表的初始化:

 双链表的打印:

双链表的尾插:

双链表的头插: 

双链表的尾删:

 

双链表的头删:

双链表pos位置之前的插入:

双链表pos位置的删除:

关于顺序表和链表的区别:


  1. 上篇文章给大家讲解了无头单向循环链表,它的特点:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构子结构,如哈希桶、图的邻接表等等。但是呢,单链表在笔试中还是很常见的。
  2. 今天我给大家讲解一下带头双向链表,它的特点:结构复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然复杂,但是使用代码实现以后会发现结构会带来很多优势。

如图,这就是今天我为大家讲解的双链表结构了。下面跟随我的思路走下去,希望让你对链表有新的理解。


 双链表的初始化:

  • 今天我给大家带来另一种方式改变链表的结构,如果想了解双指针来改变链表的结构,可以参考参考我的上一篇单链表的博客。
  • 思路:我创建了一个节点,然后把节点赋给了phead,再让它的上一个位置和下一个位置分别指向它自己,最后返回phead就是我们要的哨兵位的头节点了。
ListNode* ListCreate(ListDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}
ListNode* ListInit()
{
	ListNode* phead = ListCreate(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}


 双链表的打印:

  • 因为要让终端出现下面的样子,我就用到了打印的函数。
  • 首先,还是老套路,我先断言了一下,防止传的参数有问题。
  • 因为这里的phead是一个哨兵位,存放着无效的数据,所以,我就定义了一个cur的节点,用循环打印链表中的所有值,并标明他们的方向。

void ListPrint(ListNode* phead)
{
	assert(phead);

	printf("phead<->");
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<->", cur->val);
		cur = cur->next;
	}
	printf("\n");
}


双链表的尾插:

  • 在双链表尾插的时候,它的优势就体现出来了,如果是单链表,要尾插的话,是只有遍历找尾节点的,但是呢,如果是双向链表,phead的前一个节点就是尾节点,它就不用找尾,也不需要遍历了。这也是双链表的优势之一。
  • 尾插思路:先断言一下,然后用tail保存尾节点,创建一个新节点,然后改变尾节点和头节点链接关系,让newnode为新的尾节点。

void ListPushBack(ListNode* phead,ListDateType x)
{
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* newnode = ListCreate(x);

	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;


}


双链表的头插: 

  • 思路:头插的话,就是先保存一下头节点的位置,然后创建一个新节点,然后改变他们的链接关系就可以了。因为我是先保存了它们的位置,所以谁先链接先都可以,如果没有保存的话,要向好逻辑,不要出现找不到头节点的位置了。
void ListPushFront(ListNode* phead, ListDateType x)
{
	assert(phead);
	ListNode* newnode = ListCreate(x);
	ListNode* first = phead->next;

	phead->next = newnode;
	newnode->prev = phead;

	newnode->next = first;
	first->prev = newnode;
	
}


双链表的尾删:

  • 思路:尾删的话,就要记录一下尾节点的前一个节点了,然后去改变一下phead和尾节点前一个节点的链接关系。
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	ListNode* tail = phead->prev;
	ListNode* tailprev = phead->prev->prev;
	tailprev->next = phead;
	phead->prev = tailprev;

	free(tail);

}


双链表的头删:

  • 思路:头删的思路,其实和尾删的思路差不多,只不过这里保存的是phead之后的第二个节点了。然后就是改变链接关系。
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead != phead->next);

	ListNode* first = phead->next;
	ListNode* second = first->next;

	phead->next = second;
	second->prev = phead;

	free(first);

}

双链表pos位置之前的插入:

  • 思路:如果是插入的话,是不是和尾插和头插差不多呢,我在这里是保存的pos之前的节点,然后创建一个新的节点,让pos之前的节点指向新节点,让新节点和pos再建立连接关系。
void ListInsert(ListNode* pos, ListDateType x)
{
	assert(pos);

	ListNode* posprev = pos->prev;
	ListNode* newnode = ListCreate(x);

	posprev->next = newnode;
	newnode->prev = posprev;

	newnode->next = pos;
	pos->prev = newnode;
}


双链表pos位置的删除:

  • 思路:pos位置要删除的话,保存pos上一个节点和pos下一个节点,然后free掉pos位置,再改变他们的链接关系。
  1. void ListErase(ListNode* pos)
    {
    	assert(pos);
    	ListNode* posprev = pos->prev;
    	ListNode* posnext = pos->next;
    
    	posprev->next = posnext;
    	posnext->prev = posprev;
    	
    	free(pos);
    }

大家看了pos位置的删除和pos之前的插入,是不是感觉和前面的尾插尾删和头插头删差不多呢,其实呢,最后的两个函数是可以进行对函数的复用的。

  •  尾插:其实就是在ListInsert函数传phead就可以了,这样就实现了尾插。
	ListInsert(phead, x);
  • 头插:头插是改变phead之后的位置,所以直接传phead->next就可以了。
	ListInsert(phead->next, x);
  • 头删和尾删:因为我写的删除的函数是删除pos位置,所以传要删除的位置就可以了。
	ListErase(phead->prev);
	ListErase(phead->next);


关于顺序表和链表的区别:

  •  存储空间上:顺序表在物理上是连续的,而链表逻辑上是连续的,而物理上是不一定连续的。
  • 随机访问上:顺序表是支持随机访问的,而链表是不支持的,向要访问链表中的节点,是需要遍历的。
  • 任意位置的插入和删除:在这里链表就有很大的优势了,链表只需要改变指向就可以实现对任意位置的插入和删除。但是对于顺序表,它可能需要搬运元素,效率太低了。
  • 插入:顺序表因为是连续的,所以在插入的上面,可能会有malloc扩容,但是呢malloc是有消耗的,如果一次扩二倍,但是用的不多,就会造成对空间的浪费,如果一次扩的空间是+1,可能局面临着多次扩容,而malloc的消耗并不低,所以这是不可取的。而链表并没有容器的概念,在这方面有优势。
  • 缓存利用率:顺序表的缓存命中率高,而链表低。

#define  _CRT_SECURE_NO_WARNINGS 1
#include "DoubleList.h"

ListNode* ListCreate(ListDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

ListNode* ListInit()
{
	ListNode* phead = ListCreate(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

void ListPrint(ListNode* phead)
{
	assert(phead);

	printf("phead<->");
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<->", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

void ListPushBack(ListNode* phead,ListDateType x)
{
	assert(phead);
	//ListNode* tail = phead->prev;
	//ListNode* newnode = ListCreate(x);

	//tail->next = newnode;
	//newnode->prev = tail;

	//newnode->next = phead;
	//phead->prev = newnode;

	ListInsert(phead, x);

}

void ListPushFront(ListNode* phead, ListDateType x)
{
	assert(phead);
	//ListNode* newnode = ListCreate(x);
	//ListNode* first = phead->next;

	//phead->next = newnode;
	//newnode->prev = phead;

	//newnode->next = first;
	//first->prev = newnode;
	
	ListInsert(phead->next, x);
}

void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	//ListNode* tail = phead->prev;
	//ListNode* tailprev = phead->prev->prev;
	//tailprev->next = phead;
	//phead->prev = tailprev;

	//free(tail);
	
	ListErase(phead->prev);
}

void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead != phead->next);

	//ListNode* first = phead->next;
	//ListNode* second = first->next;

	//phead->next = second;
	//second->prev = phead;

	//free(first);

	ListErase(phead->next);
}

int ListSize(ListNode* phead)
{
	assert(phead);

	int size = 0;
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

void ListInsert(ListNode* pos, ListDateType x)
{
	assert(pos);

	ListNode* posprev = pos->prev;
	ListNode* newnode = ListCreate(x);

	posprev->next = newnode;
	newnode->prev = posprev;

	newnode->next = pos;
	pos->prev = newnode;
}

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* posprev = pos->prev;
	ListNode* posnext = pos->next;

	posprev->next = posnext;
	posnext->prev = posprev;
	
	free(pos);
}


什么是缓存利用率:

关于“Cache Line” ,缓存是把数据加载到离自己进的位置,对于CPU来说,CPU是一块一块存储的。而这就叫“Chche Line”。

我们所写的程序,其实都是会形成不同的指令,然后让CPU执行,但是呢,CPU执行速度快,内存跟不上,所以CPU一般都是把数据放到缓存中,对于小的字节来说,直接由寄存器来读取,大的字节会用到三级缓存。

简而言之,数据先被读取,然后运算,最后放到主存中,如果没有命令,就继续。

而顺序表呢,它的物理结构是连续的,它可能一开始没有命中,但是一旦缓存命中了,它可能就会被连续命中,所以这也是顺序表缓存利用率高的原因,而链表也是因为他的物理结构,导致缓存利用率低。

下面是双链表的源码:

#define  _CRT_SECURE_NO_WARNINGS 1
#include "DoubleList.h"

ListNode* ListCreate(ListDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

ListNode* ListInit()
{
	ListNode* phead = ListCreate(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

void ListPrint(ListNode* phead)
{
	assert(phead);

	printf("phead<->");
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<->", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

void ListPushBack(ListNode* phead,ListDateType x)
{
	assert(phead);
	//ListNode* tail = phead->prev;
	//ListNode* newnode = ListCreate(x);

	//tail->next = newnode;
	//newnode->prev = tail;

	//newnode->next = phead;
	//phead->prev = newnode;

	ListInsert(phead, x);

}

void ListPushFront(ListNode* phead, ListDateType x)
{
	assert(phead);
	//ListNode* newnode = ListCreate(x);
	//ListNode* first = phead->next;

	//phead->next = newnode;
	//newnode->prev = phead;

	//newnode->next = first;
	//first->prev = newnode;
	
	ListInsert(phead->next, x);
}

void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	//ListNode* tail = phead->prev;
	//ListNode* tailprev = phead->prev->prev;
	//tailprev->next = phead;
	//phead->prev = tailprev;

	//free(tail);
	
	ListErase(phead->prev);
}

void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead != phead->next);

	//ListNode* first = phead->next;
	//ListNode* second = first->next;

	//phead->next = second;
	//second->prev = phead;

	//free(first);

	ListErase(phead->next);
}

int ListSize(ListNode* phead)
{
	assert(phead);

	int size = 0;
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

void ListInsert(ListNode* pos, ListDateType x)
{
	assert(pos);

	ListNode* posprev = pos->prev;
	ListNode* newnode = ListCreate(x);

	posprev->next = newnode;
	newnode->prev = posprev;

	newnode->next = pos;
	pos->prev = newnode;
}

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* posprev = pos->prev;
	ListNode* posnext = pos->next;

	posprev->next = posnext;
	posnext->prev = posprev;
	
	free(pos);
}
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int ListDateType;

typedef struct ListNode
{
	ListDateType val;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

ListNode* ListCreate(ListDateType x);
void ListPrint(ListNode* phead);
ListNode* ListInit();
void ListPushBack(ListNode* phead,ListDateType x);
void ListPushFront(ListNode* phead, ListDateType x);
void ListPopBack(ListNode* phead);
void ListPopFront(ListNode* phead);
int ListSize(ListNode* phead);
void ListInsert(ListNode* pos, ListDateType x);
void ListErase(ListNode* pos);

 

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

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

相关文章

MyBatis-Plus实现分页查询

目录 MyBatis-Plus实现分页查询 代码 定义一个MyBatis-Plus拦截器 在连接数据库的配置文件中添加MyBatis-Plus日志查看MyBatis-Plus的SQL语句 测试 运行结果 MyBatis-Plus实现分页查询 代码 定义一个MyBatis-Plus拦截器 package com.dong.config;import com.baomidou.my…

windows下mysql的下载与安装

文章目录 1 下载2 安装目录下新建data文件夹和my.ini3 安装4设置密码与远程连接5 配置环境变量6 navicate连接成功 1 下载 官网地址 https://www.mysql.com/点击下载 社区下载 社区服务 选择版本下载 2 安装目录下新建data文件夹和my.ini my.ini 内容如下 [mysql] # 设置my…

iphone内存不足导致白苹果?可以使用这2种办法解决!

因为iPhone内存不足没及时清理导致打开任何软件闪退&#xff0c;这时很多小伙伴会重启手机来解决闪退问题&#xff0c;但就会出现白苹果问题&#xff0c;无法正常进入手机系统、实现任何操作的一种状态。 内存不足导致iPhone白苹果的问题很常见&#xff0c;可以说是苹果最常见…

linux 文件的权限

修改文件的权限 我这里有一个test.txt 文件&#xff0c;我们ll 查看一下该文件相应的属性信息 其中&#xff0c;权限的位置是相对固定的即&#xff1a; 第一个位置是r 权限&#xff0c;代表可读权限。 第二个位置是w权限&#xff0c;代表可修改权限。 第三个位置是x权限&…

【腾讯云 Cloud Studio 实战训练营】从零开始搭建一个数据大屏

文章目录 前言得到什么?使用Cloud Studio登录Cloud Studio登录方式Cloud Studio 功能介绍项目创建配置描述新建工作空间绑定Coding创建仓库绑定coding创建项目项目空间 项目搭建nuxt 脚手架Cloud Studio 安装插件nuxt初始项目预览问题描述 下载Datav 并体验页面结构展示 获取基…

【项目 线程2】3.5 线程的分离 3.6线程取消 3.7线程属性

3.5 线程的分离 #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h>void * callback(void * arg) {printf("chid thread id : %ld\n", pthread_self());return NULL; }int main() {// 创建一个子线程pthread…

Vue3基础_响应式数据

setup是组合式API 选项式API&#xff0c;是data,methods,computed&#xff0c;watch等等全都是分开的&#xff0c;但是组合式API是把这些东西全都写在一起了。 1 vue2的缺点 (1)使用vue2 Vue2版本对数据的拦截用的是Object.defineProperty, 可以监测到对象的变化。因为o…

Spring源码篇(九)自动配置扫描class的原理

文章目录 前言ClassLoader如何加载jar包里的class自动配置扫描class的原理spring中的加载方式源码总结 前言 spring是怎样通过ComponentScan&#xff0c;或者自动配置扫描到了依赖包里class的&#xff1f; ClassLoader 这里涉及到了class Loader的机制&#xff0c;有些复杂&…

信必优行业服务能力-中国头部综合性证券公司

近期召开的国家高层会议提出 “要活跃资本市场&#xff0c;提振投资者信心”&#xff0c;明确了下一阶段资本市场发展新任务、新要求&#xff0c;资本市场有望呈现新气象、新风貌。各证券公司积极响应&#xff0c;全力推进资本市场回暖&#xff1b;同时各公司也借此东风修炼内功…

【科普知识】了解电机T型速度曲线和S型速度曲线的区别!

当电机从静止状态启动并加速到额定转速时&#xff0c;其速度变化并非线性的&#xff0c;而是呈现出不同的曲线特征。T型速度曲线和S型速度曲线是两种典型的电机加速曲线类型。那它们之间有什么区别呢&#xff1f;今天&#xff0c;就让我们来深入探讨电机加速曲线的奥秘。 电机速…

国内是不是很缺音视频的开发人员,想学习音视频开发

第一、音视频开发人员的培养是一个长期投入&#xff0c;见效慢的过程&#xff0c;不像有些培训机构&#xff0c;半年培训就可以出去找工作了。同时培训机构最终的目的是快速培训&#xff0c;推荐工作然后挣钱。而音视频开发见效太慢&#xff0c;没有一定时间的锻炼和项目喂养&a…

骨传导耳机什么牌子好?盘点最受欢迎的几款骨传导耳机

骨传导耳机最近一两年越来越受欢迎&#xff0c;市场上不同形态的非入耳式耳机都有&#xff0c;从骨传导&#xff0c;夹耳式到气传导等等都有。骨传导耳机的好处有很多&#xff0c;非入耳式&#xff0c;不伤耳朵&#xff0c;佩戴更舒适更安全。但是一直以来&#xff0c;骨传导耳…

如何把非1024的采样数放入aac编码器

一. aac对数据规格要求 二、代码实现 1.初始化 2.填入数据 3.取数据 三.图解 一. aac对放入的采样数要求 我们知道aac每次接受的字节数是固定的&#xff0c;在之前的文章里有介绍libfdk_aac音频采样数和编码字节数注意 它支持的采样数和编码字节数分别是&#xff1a; fdk_aac …

中规院:2023年黄河流域主要城市人居环境气象评估报告(附下载

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 黄河流域年均高温天数呈上升趋势&#xff0c;近五年达到历史高位。 黄河流域年均高温天数由1978年的11日升至2022年的17日&#xff0c;整体呈上升趋势。1978-2022年间&#xff0c;2018年年均高温天数…

AutoCAD项目外包开发流程

进行AutoCAD的二次开发&#xff0c;一般常用的开发工具是AutoCAD的官方开发工具--ObjectARX。这是一个面向对象的C的开发环境&#xff0c;通过ObjectARX&#xff0c;我们可以直接获取AutoCAD的内核级别的信息&#xff0c;可以大幅度的开发出高效的程序。今天和大家分享这方面的…

【Leetcode刷题】位运算

本篇文章为 LeetCode 位运算模块的刷题笔记&#xff0c;仅供参考。 位运算的常用性质如下&#xff1a; a ^ a 0 a ^ 0 a a ^ 0xFFFFFFFF ~a目录 一. 基本位运算Leetcode29.两数相除Leetcode89.格雷编码 二. 位运算的性质Leetcode136.只出现一次的数字Leetcode137.只出现一…

Python(六十一)字典元素的获取

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

如何在IDEA使用数据库(新手教程)

第一步:将数据库服务器打开 进入你下载好的数据库路径到bin目录下cmd打开命令提示符界面 确认启动数据库 第二步&#xff1a;打开IDEA&#xff0c;找到IDEA数据库 第三步&#xff1a;选择要连接数据库 第四步&#xff1a;根据数据库信息连接数据库 连接之前的页面 输入我们要的…

2022年江西省职业院校技能大赛网络搭建与应用赛项正式赛卷

2022年江西省职业院校技能大赛网络搭建与应用赛项正式赛卷 操作题总分900分&#xff0c;竞赛时长3.5小时 竞赛说明&#xff1a; 1.禁止携带和使用移动存储设备、计算器、通信工具及参考资料。 2.请根据大赛所提供的比赛环境&#xff0c;检查所列的硬件设备、软件及文档清单、…

Java使用POI读取Excel名称管理器

文章目的 本文主要介绍如何使用poi读取到Excel的名称管理器中的内容。并且定位到单元格。 在企业的开发中可能需要通过名称管理器定位到某个单元格&#xff0c;然后在单元格上生成签名。 环境配置 Java&#xff1a;Jdk1.8 poi&#xff1a;5.2.3 maven依赖(pom.xml)&#x…