C++数据结构笔记(3)线性表的链式存储底层实现

news2024/10/4 12:28:06

本系列的帖子并不包含全部的基础知识,只挑一部分最核心的知识点总结,着重于具体的实现细节而并非理论的知识点总结,大家按需阅读学习。


链表的核心概念总结如下:

1.链式存储不需要连续的内存空间

2.链表由一系列的结点组成,每个节点包含两个域,分别是指针域和数据域

3.C语言中能指向任何类型的指针为空指针:void*

4.链表在具体实现时,不需要引入容量的概念。

话不多说,直接上代码,和前文顺序结构的实现方法一样,首先声明独立的LinkList.h头文件,如下:

#ifndef LINKLIST_H
#define LINKLIST_H

#include<stdlib.h>
#include<stdio.h>

//链表结点
typedef struct LinkNode{
	void* data;
	//空指针,指向任何类型的数据
	struct LinkNode* next; 
}LinkNode;
 
//链表结构体 
typedef struct LinkList{
	LinkNode* head;
	int size;
	//对于链表来说,不需要有容量的定义 
}LinkList;
//打印函数指针
typedef void(*PRINTLINKNODE)(void*); 
//意义在于打印时可根据用户使用的类型自定义打印 


//初始化链表
LinkList* Init_LinkList();
//指定位置插入元素
void Insert_LinkList(LinkList* list,int pos,void* data);
//删除指定位置的值
void RemoveByPos_LinkList(LinkList* list,int pos);
//获得链表的长度
int Size_LinkList(LinkList* list);
//根据指针查找 
int Find_LinkList(LinkList* list,void* data);
//返回第一个节点的位置
void* Front_LinkList(LinkList* list); 
//打印链表结点 
void Print_LinkList(LinkList* list,PRINTLINKNODE print);
//释放链表内存
void FreeSpace_LinkList(LinkList* list);

        需要注意的是,在本段头文件中声明了两个类型的结构体:即结点类型LinkNode链表类型LinkList,原因是链表的元素(结点)必须包含地址域(指针);此外,与顺序表不同的是,链表并不需要预留初始的容量

        此外,void* data这个写法,保证链表类型中寸放的数据部局限于一种,可以在测试函数中由开发者自定义;对于typedef void(*PRINTLINKNODE)(void*),同理,开发者可以根据自定义的类型自由修改打印遍历的函数。

 如下的LinkList.c文件中,实现了链表的各种具体操作,具体和顺序存储的思路类似。

引入头文件

#include"LinkList.h"

初始化链表

//初始化链表
LinkList* Init_LinkList(){
	
	LinkList* list=(LinkList*)malloc(sizeof(LinkList));
	list->size=0; 
	
	//声明头结点,不需要用来保存数据信息
	list->head=(LinkNode*)malloc(sizeof(LinkNode));
	list->head->data=NULL;
	list->head->next=NULL;
	
	return list;
}

注意,头指针在初始化时均为空,其实也可以直接省去头指针的定义,但如果不定义的话后期的处理会复杂化。

指定位置插入元素(重点)

//指定位置插入元素
void Insert_LinkList(LinkList* list,int pos,void* data){
	if(list==NULL||data==NULL)
		return;
	//pos越界,则默认插入到尾部
	if(pos<0||pos> list->size)
		pos=list->size;
	
	//创建新结点
	LinkNode* newnode=(LinkNode*)malloc(sizeof(LinkNode));
	newnode->data=data;
	newnode->next=NULL;
	//首先要找到原位置上的当前结点
	LinkNode* pCurrent=list->head;
	for(int i=0;i<pos;i++)
		pCurrent=pCurrent->next;
		//体现链表性质的一个操作
		//即,不能通过下标访问元素,必须从头开始依次向后遍历,非常遗憾 
	
	//新结点入链表
	newnode->next=pCurrent->next;
	pCurrent->next=newnode;
	
	list->size++;
}

上述写法是符合链表性质的一种定义,核心点在于,找到需要插入结点的位置pos上原本的结点,并保存下来,然后让新的结点指向原结点的后继结点,再将原结点的后继结点指向当前的新结点,如下图所示:

删除指定位置的值

void RemoveByPos_LinkList(LinkList* list,int pos){
	if(list==NULL)
		return;
	
	if(pos<0||pos>= list->size)
		return;
	//查找删除结点的前一个结点! 
	LinkNode* pCurrent=list->head;
	for(int i=0;i<pos;i++)
		pCurrent=pCurrent->next;
	//缓冲删除的结点
	LinkNode* pDel=pCurrent->next;
	pCurrent->next=pDel->next;
	//删除结点的内存
	free(pDel);
	list->size--; 
	
}

与插入结点的操作类似,区别在于需要额外开辟空间保存当前结点;此外,遍历时同样必须从头到尾;最后还要将链表的元素数量减一。

获得链表的长度

//获得链表的长度
int Size_LinkList(LinkList* list){
	return list->size;

}

根据指针查找结点元素

//根据指针查找 
int Find_LinkList(LinkList* list,int pos){
	if(list==NULL)
		return -10;
	if(pos<0||pos>= list->size)
		return -10;	
	LinkNode* pCurrent=list->head->next;
	//直接指向第一个有效数据
	int i=0;
	while(pCurrent!=NULL){
		if(pCurrent!=NULL)
			break;
		i++;
		pCurrent=pCurrent->next;
		
	} 
	return i;
	
}

返回第一个结点的位置

//返回第一个节点的位置
void* Front_LinkList(LinkList* list){
	return NULL;
	return list->head->next;
}

打印链表中的结点

//打印链表结点 
void Print_LinkList(LinkList* list,PRINTLINKNODE print){
	if(list==NULL)
		return;
	LinkNode* pCurrent=list->head->next;
	while(pCurrent!=NULL)
	{
		print(pCurrent->data);
		pCurrent=pCurrent->next;
	}
}

释放链表内存

//释放链表内存
void FreeSpace_LinkList(LinkList* list){
	if(list==NULL)
		return;
	LinkNode* pCurrent=list->head;
	while(pCurrent!=NULL)
	{
		//缓存档期结点
		LinkNode* pNext= pCurrent->next;
		free(pCurrent);
		pCurrent=pNext; 
	}
	list->size=0;
	free(list);
	
}

接下来通过main函数实现测试,运行结果如下图:

#include <iostream>
#include<stdlib.h>
#include<stdio.h>
#include "LinkList.h"
using namespace std;

//定义测试类型
typedef struct test{
	char name[64];
	int age;
	int score;
}test; 

//打印函数
void Myprint(void* data)
{
	test* t=(test*)data;
	//此时开发人员知道自己所定义的类型!
	cout<<(t->name)<<" "<<(t->age)<<" "<<(t->score)<<endl;
} 

int main(int argc, char** argv) {
	
	
	LinkList* list=Init_LinkList();
	test t1={"JSL",21,7371};
	test t2={"HYH",21,7166};
	test t3={"Final",1325,131917};
	
	//插入链表
	Insert_LinkList(list,0,&t1);
	Insert_LinkList(list,1,&t2);
	Insert_LinkList(list,2,&t3);
	
	Print_LinkList(list,Myprint);
	FreeSpace_LinkList(list);
	
	
	return 0;
}

 

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

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

相关文章

阿里云ECS U实例评测

参与ECSU实例评测&#xff0c;申请免费体验机会&#xff1a;https://developer.aliyun.com/mission/review/ecsu What u1实例是什么&#xff1f; u1实例本质上还是ecs服务器&#xff0c;但是是阿里云推出的一种新型实例规格族 阿里云根据使用场景和业务场景将ecs划分为不同的…

如何使用低代码推动企业数字转型

企业创建其所需数字解决方案的方式和速度正在发生历史性变化。企业可以通过各种方式来实现这一转型&#xff0c;低代码作为其中一种转型方式&#xff0c;也越来越受到企业的喜欢&#xff0c;帮助企业适应不断变化的条件。下面我们用天翎低代码平台为例&#xff0c;浅谈一下&…

人脸考勤签到升级篇

目录 编写签到成功页面&#xff08;移动端&#xff09; 实现考勤成功页面&#xff08;持久层&#xff09; 实现考勤成功页面&#xff08;业务层&#xff09; 实现考勤成功页面&#xff08;Web层&#xff09; 一、查询用户的入职日期 二、实现查询考勤结果的Web方法 实现…

8年资深测试总结,性能测试-性能内存详解,揭秘你的疑惑...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 缓存 从 free 命令…

python实现基于SVD矩阵分解的电影推荐系统设计

大家好&#xff0c;我是带我去滑雪&#xff01; SVD 是一种矩阵分解技术&#xff0c;通过将空间维数从 N 维降到 K 维&#xff08;其中K<N&#xff09;来减少数据集的特征数。在推荐系统中&#xff0c;SVD 被用作一种协同过滤技术。使用矩阵结构&#xff0c;其中行表示用户&…

CC2530 外部中断配置步骤

第一章 硬件原理图分析 第二章 配置按键中断步骤

Python:通过飞书API接口发送通知消息

通过飞书发送应用消息&#xff0c;及时收到线上异常消息 总的来说&#xff0c;飞书消息发送的前戏&#xff0c;要比企业微信和钉钉稍复杂 相关连接 官网 https://www.feishu.cn/管理后台 https://www.feishu.cn/admin开放平台 https://open.feishu.cn/ 参数准备 首先&…

JS中的函数与数组

文章目录 函数的定义与调用①、函数定义②、函数调用 函数的参数与返回值let与var关键字区别创建数组并使用数组遍历 函数的定义与调用 ①、函数定义 函数定义有三种方式&#xff1a; 第一种&#xff1a;function 函数名(){ 函数体 } 第二种&#xff1a;定义变量的语法&…

LITE TRANSFORMER WITH LONG-SHORT RANGE ATTENTION

1.摘要 在这篇论文中&#xff0c;我们提出了一种高效的移动NLP架构——Lite Transformer&#xff0c;以在边缘设备上部署移动NLP应用。Transformer已经成为自然语言处理&#xff08;例如机器翻译、问答系统&#xff09;中无处不在的技术&#xff0c;但要实现高性能需要大量计算…

Elasticsearch 初步使用

本文是 Elasticsearch官方博客文档 阅读笔记记录&#xff0c;详细内容请访问官方链接&#xff0c;本文只做重点记录 index索引 对于经常看 Elastic 英文官方文档的开发者来说&#xff0c;我们经常会看到 index 这个词。在英文中&#xff0c;它即可以做动词&#xff0c;表示建…

ASP.Net Core Web API快速搭建后台服务搭载SQLServer+FreeSQL(一)

目录 一.建立WebAPI所需要的环境 1. IDE编辑器&#xff1a;VisualStudio2019 2.数据库安装&#xff1a;SqlServer 3.下载SQL Server Management Studio (SSMS) 二.创建ASP.Net Core Web API工程 1.创建模板工程 2. 试运行案例接口 3.安装FreeSQL工具包 三.设计数据库 启…

《养生大世界》简介及投稿要求

《养生大世界》简介及投稿要求 《养生大世界》是由国家新闻总署备案&#xff0c;中国老年保健协会主管的国家级学术期刊&#xff0c;全国公开发行正规刊物。 《养生大世界》传播健康中国文化理念&#xff0c;推动健康养生科技创新发展&#xff0c;助力健康产业惠及人民。 主…

蓝桥杯单片机竞赛主观题总结(全)(2.5W字)

前言 对于蓝桥杯的单片机竞赛&#xff0c;主观题很重要&#xff0c;占了百分之70-80的大头&#xff0c;因此主观题做得怎么样决定了比赛是否能拿奖&#xff0c;而客观题的正确率很大程度上决定了你能否获得省一&#xff0c;从而进入决赛。因此我在这里分享一期关于主观题中各个…

95道MongoDB面试题

1、mongodb是什么&#xff1f; MongoDB 是由 C语言编写的&#xff0c;是一个基于分布式文件存储的开源数据库系统。 再高负载的情况下&#xff0c;添加更多的节点&#xff0c;可以保证服务器性能。 MongoDB 旨在给 WEB 应用提供可扩展的高性能数据存储解决方案。 MongoDB 将数…

前端技术栈 - ES6 - Promise -模块化编程

文章目录 &#x1f55b;ES6⚡let变量⚡const常量⚡数组解构⚡对象解构⚡模板字符串⚡对象声明简写⚡对象方法简写⚡对象运算符扩展⚡箭头函数&#x1f98a;传统函数>箭头函数&#x1f98a;箭头函数对象解构 ⚡作业布置 &#x1f550;Promise⚡需求分析⚡传统ajax回调嵌套⚡p…

GO、KEGG(批量分组)分析及可视化

这篇帖子其实是更新、补充、解决一下问题的。我们号写过很多GO、KEGG富集分析的可视化&#xff0c;数不胜数&#xff0c;可以在公众号检索“富集”了解更多。我们演示的时候都是直接提供了富集的结果文件&#xff0c;一般演示为了图方便&#xff0c;也是利用在线工具cytoscape做…

网络工程师的副业,能有多野?

大家好&#xff0c;我是许公子。 在网工这行做了这么久&#xff0c;经常会有同事或者朋友来问我&#xff0c;有没有什么搞副业的路子。 别说&#xff0c;选择还真挺多。 前两天的文章里&#xff0c;这位朋友就秀了一波&#xff0c;这工作速度、便捷程度、收款金额&#xff0c…

Java并发编程-线程基础

哈喽,大家好,这篇文章主要讲解Java并发编程,线程基础的知识点,讲解知识点的同时也会穿插面试题的讲解. 主要讲解以下内容 进程,线程基础常见的一些区别比较如何使用和查看线程理解线程如何运行以及线程上下文切换的知识线程方法线程状态 希望能给大家带来帮助.加油~~~ 目录 进…

自学黑客(网络安全),一般人我劝你还是算了吧(自学网络安全学习路线--第十九章 口令破解与防御技术下)【建议收藏】

文章目录 一、自学网络安全学习的误区和陷阱二、学习网络安全的一些前期准备三、自学网络安全学习路线一、口令攻击的综合应用1、Windows NT, 2000口令攻击2、Windows XP, 2003口令攻击3、Unix系统口令攻击4、远程口令攻击 二、口令攻击的防御1、口令攻击防御概述2、保持口令的…

RS485转Profinet通讯

RS485转Profinet通讯 概述系统组成流量积算仪网关 软件总结 概述 一个支持RS485的流量积算仪的数据要被Profinet的PLC读取。制作一个网关&#xff0c;实现RS485到Profinet的转换。 系统组成 流量积算仪 支持RS485通讯&#xff0c;通讯协议是modbus RTU。采用功能码3可以读取…