数据结构——双链表实现和注释浅解

news2024/9/19 23:37:41

关于双链表的基础部分增删查改的实现和一点理解,写在注释里~ 


前言 

 

 

 

 

 

 


浅记

 

1. 哨兵位的节点不能被删除,节点的地址也不能发生改变,所以是传一级指针

2. 哨兵位并不存储有效数据,所以它并不是有效节点

3. 双向链表为空时,说明只剩下一个头节点(哨兵位)


 List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LTDataType;
//定义双向链表节点的结构
typedef struct ListNode
{
	LTDataType data;//存储的数据
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向前一个节点的指针
}LTNode;


//声明双向链表中提供的方法

//申请节点
LTNode* LTBuyNode(LTDataType x);



//初始化
//void LTInit(LTNode** pphead);
LTNode* LTInit();
//销毁
void LTDesTroy(LTNode* phead);
//打印
void LTPrint(LTNode* phead);

//插入数据之前,链表必须初始化到只有一个头结点的情况
// 
//不改变哨兵位的地址,因此传一级即可
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);

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


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


 List.c

尾插

//哨兵位的节点不能被删除,节点的地址也不能发生改变,所以是传一级指针

//尾插
//phead:哨兵位
void LTPushBack(LTNode* phead, LTDataType x)
{
	//断言
	assert(phead);
	//申请新节点
	LTNode* newnode = LTBuyNode(x);
	
	//phead  phead->prev(前尾节点)  newnode
	//先将新节点的prev指针指向哨兵位的prev指针(前尾节点)
	//再把新节点的next指针指向哨兵位
	newnode->prev = phead->prev;
	newnode->next = phead;

	//先将哨兵位的prev指针(前尾节点)指向新节点
	//再把哨兵位的prev指针指向新节点
	phead->prev->next = newnode;
	phead->prev = newnode;

}

头插

在第一个存储有效数据的节点之前插入

//头插
//在第一个存储有效数据的节点之前插入
//先去修改不会受到影响的节点,也就是新节点(newnode)
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	//phead newnode phead->next(第一个存储有效数据的节点d1)
	//新节点的next指针指向第一个存储有效数据的节点d1
	//新节点的prev指针指向哨兵位
	newnode->next = phead->next;
	newnode->prev = phead;
	//第一个存储有效数据的节点d1的prev指针指向新节点
	//哨兵位的next指针指向新节点
	phead->next->prev = newnode;
	phead->next = newnode;
}

尾删


//尾删
void LTPopBack(LTNode* phead)
{
	//链表必须有效且链表不能为空(只有一个哨兵位)
	assert(phead && phead->next != phead);
	//创建一个临时变量del,把最后一个存储有效数据的节点存放进去(d3)
	LTNode* del = phead->prev;
	//phead del->prev(d2) del(d3)
	//将d2的next指针指向哨兵位
	//把哨兵位的prev指针指向d2
	del->prev->next = phead;
	phead->prev = del->prev;

	//删除del(d3)节点,置为空
	free(del);
	del = NULL;
}

 

头删 

删除第一个存储有效数据的节点

//头删
//删除第一个存储有效数据的节点
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	//创建一个临时变量del,把最后一个存储有效数据的节点存放进去(d1)
	LTNode* del = phead->next;

	//phead del(d1) del->next(d2)
	//将哨兵位的next指针指向d2
	//把d2的prev指针指向哨兵位
	phead->next = del->next;
	del->next->prev = phead;

	//删除del节点
	free(del);
	del = NULL;
}

 

 汇总

#include"List.h"



//data存储的数据
//next指向下一个节点的指针
//prev指向前一个节点的指针


//哨兵位的节点不能被删除,节点的地址也不能发生改变,所以是传一级指针
//哨兵位并不存储有效数据,所以它并不是有效节点


//申请节点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	//node的next指针和prev指针都指向node本身 自循环
	//哨兵位
	node->next = node->prev = node;

	return node;
}

//插入数据之前,链表必须初始化到 只有一个头结点 的情况

//初始化
//void LTInit(LTNode** pphead);
LTNode* LTInit()
{
	//初始化就是申请一个哨兵位,哨兵位是自循环和
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

//销毁
void LTDesTroy(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//此时pcur指向phead,而phead还没有被销毁
	free(phead);
	phead = NULL;
}

//打印
void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}



//哨兵位的节点不能被删除,节点的地址也不能发生改变,所以是传一级指针

//尾插
//phead:哨兵位
void LTPushBack(LTNode* phead, LTDataType x)
{
	//断言
	assert(phead);
	//申请新节点
	LTNode* newnode = LTBuyNode(x);
	
	//phead  phead->prev(前尾节点)  newnode
	//先将新节点的prev指针指向哨兵位的prev指针(前尾节点)
	//再把新节点的next指针指向哨兵位
	newnode->prev = phead->prev;
	newnode->next = phead;

	//先将哨兵位的prev指针(前尾节点)指向新节点
	//再把哨兵位的prev指针指向新节点
	phead->prev->next = newnode;
	phead->prev = newnode;

}

//头插
//在第一个存储有效数据的节点之前插入
//先去修改不会受到影响的节点,也就是新节点(newnode)
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	//phead newnode phead->next(第一个存储有效数据的节点d1)
	//新节点的next指针指向第一个存储有效数据的节点d1
	//新节点的prev指针指向哨兵位
	newnode->next = phead->next;
	newnode->prev = phead;
	//第一个存储有效数据的节点d1的prev指针指向新节点
	//哨兵位的next指针指向新节点
	phead->next->prev = newnode;
	phead->next = newnode;
}

//尾删
void LTPopBack(LTNode* phead)
{
	//链表必须有效且链表不能为空(只有一个哨兵位)
	assert(phead && phead->next != phead);
	//创建一个临时变量del,把最后一个存储有效数据的节点存放进去(d3)
	LTNode* del = phead->prev;
	//phead del->prev(d2) del(d3)
	//将d2的next指针指向哨兵位
	//把哨兵位的prev指针指向d2
	del->prev->next = phead;
	phead->prev = del->prev;

	//删除del(d3)节点,置为空
	free(del);
	del = NULL;
}
//头删
//删除第一个存储有效数据的节点
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	//创建一个临时变量del,把最后一个存储有效数据的节点存放进去(d1)
	LTNode* del = phead->next;

	//phead del(d1) del->next(d2)
	//将哨兵位的next指针指向d2
	//把d2的prev指针指向哨兵位
	phead->next = del->next;
	del->next->prev = phead;

	//删除del节点
	free(del);
	del = NULL;
}


//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

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


//删除pos节点
void LTErase(LTNode* pos)
{
	//pos理论上来说不能为phead,但是没有参数phead,无法增加校验
	assert(pos);
	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}


//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}

Test.c 

#include"List.h"

void ListTest01()
{
	//LTNode* plist = NULL;
	//LTInit(&plist);
	LTNode* plist = LTInit();

	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPrint(plist);

	LTNode* find = LTFind(plist, 3);
	//LTInsert(find, 66);
	LTErase(find);
	find = NULL;

	LTPrint(plist);
	LTDesTroy(plist);
	//plist = NULL;


	//if (find == NULL)
	//{
	//	printf("找不到!\n");
	//}
	//else {
	//	printf("找到了!\n");
	//}

	//LTPushFront(plist, 1);
	//LTPrint(plist);
	//LTPushFront(plist, 2);
	//LTPrint(plist);
	//LTPushFront(plist, 3);
	// 
	//
	测试尾删
	//LTPopBack(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);

	测试头删
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);
}

int main()
{
	ListTest01();
	return 0;
}

一点浅解,感谢观看~

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

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

相关文章

单点登录:cas单点登录实现原理浅析

cas单点登录实现原理浅析 一晃几个月没写博客了&#xff0c;今年多灾多难的一年。 安能摧眉折腰事权贵&#xff0c;使我不得开心颜&#xff01; 财富是对认知的补偿&#xff0c;不是对勤奋的嘉奖。勤奋只能解决温饱&#xff0c;要挣到钱就得预知风口&#xff0c;或者有独到见解…

探寻 IP 代理地址繁多之因

在当今的网络天地里&#xff0c;IP 代理服务随处可见&#xff0c;且令人称奇的是&#xff0c;它们常常手握海量的 IP 地址可供挑选。那么&#xff0c;究竟是什么原因使得 IP 代理拥有如此众多的地址呢&#xff1f;现在&#xff0c;就让我们一同深入探究这个神秘现象背后的缘由。…

Camunda调用子流程案例

调用子流程 调用子流程是指子流程在主流程的外面。子流程一般是多个流程可重用的流程&#xff0c;也可以独立调用子流程。 可以对比编程中的方法抽取。子流程运行时&#xff0c;主流程也是等待状态。子流程结束&#xff0c;主流程继续。 BPMN设计 主流程 全局配置 上传视频 处…

并查集基础与简单扩展应用

并查集 基础题目路径压缩 扩展应用扩展题目1扩展题目2 并查集的结构是一棵树 并查集有两种功能&#xff0c;一种是判断两个元素是否在同一集合&#xff0c;第二种是合并两个集合 并查集的实现需要记录每个节点的父亲节点 判断两个元素是否在同一集合&#xff0c;即判断两个元…

ARM编程模型、指令集、ARM汇编语言程序设计

一、编程模型 1.1数据和指令类型 在之后的演示当中&#xff0c;我们大多数将采用ARM指令集 1.2处理器工作模式 1.3ARM寄存器 1.3.1分类 &#xff08;1&#xff09; 31 个通用寄存器&#xff0c;包括 PC&#xff08;程序计数器&#xff09;在内&#xff0c;都是 32 位的寄存器…

实习项目|苍穹外卖|day7

缓存菜品 1.根据原型进行需求分析与设计&#xff08;接口文档&#xff09; 2.根据接口设计DTO&#xff08;redis数据类型选取&#xff09; 3.编码controller-》service-》mapper GetMapping("/list")ApiOperation("根据分类id查询菜品")public Result<…

51单片机-第十三节-直流电机驱动(PWM)

一、直流电机介绍&#xff1a; 直流电机是一种将电能转换为机械能的装置。 一般的直流电机有两个电极&#xff0c;电极正接&#xff0c;电机正转&#xff0c;电极反接&#xff0c;电机反转。 直流电机主要由永磁体&#xff08;定子&#xff09;、线圈&#xff08;转子&#…

GB35114 USC安防平台 中星微国密摄像机配置 流程

中星微国密摄像机配置介绍 如下以中星微VS-IPC8021S-Y-T4摄像机为例&#xff0c;需要先各自获取p10文件&#xff0c;并通过证书签发机构或者测试SM2证书签发获取证书。 网络配置如下: 摄像机的IP地址为192.168.1.108&#xff0c;国标ID为34020000001320000015 系统的IP地址…

Robotframework框架基础

1.Robot Framework是开源的自动化测试框架&#xff0c;基于关键字驱动的测试方法2.它提供用于创建和执行自动化测试的工具和库&#xff0c;并支持使用不同的测试库和插件进行扩展 一.以下是Robot Framework框架的基础知识 1. 安装&#xff1a;通过pip安装Robot Framework和相…

打造可视化数字大屏供应链管理平台详解:从食堂采购系统源码开始

这篇文章将深入探讨直播美颜SDK与主播美颜工具的技术原理及其发展趋势&#xff0c;帮助开发者了解如何通过技术手段实现流畅、高效的实时美颜效果。 一、什么是直播美颜SDK&#xff1f; 直播美颜SDK是一种为开发者提供实时美颜功能的集成开发工具包。它通过对摄像头捕捉到的画…

现代计算机中数字的表示与浮点数、定点数

现代计算机中数字的表示与浮点数、定点数 导读&#xff1a;浮点数运算是一个非常有技术含量的话题&#xff0c;不太容易掌握。许多程序员都不清楚使用操作符比较float/double类型的话到底出现什么问题。这篇文章讲述了浮点数的来龙去脉&#xff0c;所有的软件开发人员都应该读…

轻松搞定用户认证:微搭低代码平台打造完美登录体验01用户登录

目录 1 创建数据源2 搭建后端API3 用户登录4 最终的代码总结 欢迎阅读我们的微搭低代码全栈开发课程&#xff0c;这是我们的第二篇。在第一篇中我们整体描述了小程序的功能结构&#xff0c;这一篇我们就进入实际的开发。 在开发小程序的时候&#xff0c;第一个需要考虑的就是用…

数据结构基础讲解(一)——线性表之顺序表专项练习

本文数据结构讲解参考书目&#xff1a; 通过网盘分享的文件&#xff1a;数据结构 C语言版.pdf 链接: https://pan.baidu.com/s/159y_QTbXqpMhNCNP_Fls9g?pwdze8e 提取码: ze8e 目录 前言 一.线性表的定义 二.线性表的基本操作 三.线性表的顺序存储和表示 四.顺序表中基本操作…

如何设置好看的电脑屏保?电脑屏保设置教程

如何设置好看的电脑屏保&#xff1f;电脑屏保设置教程。大家好&#xff0c;今天小编给大家带来了好看的电脑屏保&#xff0c;教大家如何设置一个好看的电脑屏保。屏保软件很多&#xff0c;今天我们介绍一款比较有特殊的屁屏保软件&#xff1a;芝麻时钟&#xff08;芝麻时钟 桌面…

【C++二分查找】1760. 袋子里最少数目的球

本文涉及的基础知识点 C二分查找 LeetCode1760. 袋子里最少数目的球 给你一个整数数组 nums &#xff0c;其中 nums[i] 表示第 i 个袋子里球的数目。同时给你一个整数 maxOperations 。 你可以进行如下操作至多 maxOperations 次&#xff1a; 选择任意一个袋子&#xff0c;并…

AMEsim和Simulink联合仿真生成新的.mexw64液压模型文件

AMEsim和Simulink进行联合仿真非常重要的就是AMEsim经过第四阶段Simulation会在相同文件下面生成一个与AMEsim液压模型相同名字的.mexw64文件&#xff0c;在Simulink进行联合仿真的S-Function需要找的也就是这个文件&#xff0c;只不过输入的时候除了液压模型名字之外&#xff…

形态学运算合集

圆形结构元素 禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码

Post-Training有多重要?一文带你了解全部细节

1. 简介 随着LLM学界和工业界日新月异的发展&#xff0c;不仅预训练所用的算力和数据正在疯狂内卷&#xff0c;后训练&#xff08;post-training&#xff09;的对齐和微调方法也在不断更新。InstructGPT、WebGPT等较早发布的模型使用标准RLHF方法&#xff0c;其中的数据管理风…

Git撤销add

git要提交版本第一步是add&#xff0c;就算是文件本身已经存在只是修改&#xff0c;也需要添加&#xff0c;即添加到暂存区。其中最偷懒和也保险的命令是&#xff1a; git add . 即添加了本地&#xff08;多称工作目录&#xff09;所有文件。 撤销add有以下文章&#xff1a; …

解决SRS流媒体服务服务器无法接收客户端ipv6 RTMP推流的思路

这篇短文我不介绍SRS是什么&#xff0c;主要介绍一个场景问题&#xff0c;场景是你使用服务器并且部署了SRS服务配置成一个媒体流转发服务&#xff0c;也就是客户端往SRS流媒体服务器推流&#xff0c;然后SRS把流转推出去&#xff0c;但是会涉及到一个问题是&#xff1a;用户客…