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

2023-11-03

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


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

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;
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 自学python转行-强烈建议

    原标题 强烈建议 转行Python最好看一下这篇文章 一 转行要趁早 上面类似的问题群里还有很多 我请了一些不同岗位的嘉宾来给大家分享经验 下面谈谈我的感悟 1 转行如爬树 转行真的要趁早 年轻的时候 试错的代价很小 即使你走错了路还能转回
  • Typora 远程代码执行漏洞

    声明 本文仅用于技术交流 请勿用于非法用途 由于传播 利用此文所提供的信息而造成的任何直接或者间接的后果及损失 均由使用者本人负责 文章作者不为此承担任何责任 一 产品介绍 Typora 是一款由 Abner Lee 开发的轻量级 Mark
  • 软件测试管理

    软件测试 在规定的条件下对程序进行操作 以发现程序的错误 衡量软件质量 并对其是否能满足用户需求进行评估的过程 点工 项目研发 1 立项 立项依据 商业论证 可行性分析报告 编写角色 市场人员 项目经理 产品经理 内容 分析项目的成本和收益
  • 2020-2021前端面试题合集,结果蒙了

    正文 介绍下半连接队列 服务器第一次接收到客户端的SYN后 会处于SYN REVD阶段 此时双方还没有建立完全的连接 服务器会把此种状态下请求连接放在一个队列里 我们把这种队列称为半连接队列 已经完成三次握手并建立连接 就叫全连接队列 ht
  • 对顺序表中元素从小到大排序的算法

    编写一个对顺序表中元素从小到大排序的算法 函数接口如下 初始条件 线性表L已经存在 且元素个数大于1 操作结果 将L中的元素从小到大排序 Status ListSort Sq SqList L 然后 在main函数中调用ListSort S
  • 两个变量进行值交换的三种方式

    在很多应用场景中我们需要将两个变量的值进行交换 在这里总结了3种方式以及它们的利弊 第一种 可以定义一个额外的变量用于进行数值交换 这是种最普通但实用性又很强的方法 int a 1 int b 2 int c a c 1 a b a 2 b
  • IOC控制反转

    IOC控制反转 核心思想 控制反转思想的核心就是把对象的控制权的 反转 对象的控制权包括对象的创建 管理 调用等 在对象的生命周期的各个阶段进行相应的管理 由编码人员交还给 程序本身 去管理 这屏蔽了对象创建和管理等过程 使得程序员可以将精
  • 刷脸支付带给人们真切的便捷和科技感

    刷脸支付异军突起的背后 是国内人脸识别功能等人工智能新技术与完备的硬件基础 通讯基础和数据基础共同作用的结果 随着今年5G的大规模商用 5G必然为其提供更多应用可能性 让刷脸支付与不同平台 企业 场景中的协作创新更加完备 可以预见 当刷脸支
  • 嵌入式开发人员-经历汇总

    目录 1 迷茫与前行 2 嵌入式到底该怎么学 2 1 单片机开发 2 2 Linux应用开发 3 嵌入式技术学习路线分享 4 单片机实习经历 2021年秋招记录 怀科同学 申明本文旨在为嵌入式工作提供入门建议 搬运总结 也会有自己的经验总结
  • 图论-最短路问题

    首先明确一个代码实现时候的问题 为什么使用0x3f3f3f3f而不是0x7f7f7f7f来表示无穷大INF 虽然0x7f7f7f7f确实更大而且符号位是0 但是在最短路算法中 经常出现形如min a a b 的表达式 如果此时a与b都是IN
  • AI焦虑潮下,打工人的抵抗、转向、破局

    一股 AI让人下岗 的焦虑 正在蔓延 蔓延到了 这里 不同于区块链 元宇宙和web3 2023年的这股AI浪潮真正席卷了所有人 在大厂 大佬和投资人们为船票激烈角逐的同时 Midjouney ChatGPT Notion AI等工具的惊人效
  • 闲鱼副业是什么?闲鱼副业应该怎么做?

    闲鱼副业是什么 闲鱼副业应该怎么做 有人会问 闲鱼现在还能做吗 还能赚钱吗 对于这样的问题 我只想说 其实再不好的行业 都有赚钱的牛人 再赚钱的领域 同样也有挣不到钱的人 所以 对于赚钱 有一句话是这样说的 没有不赚钱的项目 只有不会赚钱的
  • GPT专业应用:撰写工作简报

    图片由Lexica 生成 输入 Workers working overtime 工作简报 作为一种了解情况 沟通信息的有效手段 能使上级机关和领导及时了解 掌握所属部门的政治学习 军事训练 行政管理等方面的最新情况 同时 能和同级单位 部
  • FLEX & BISON 联合使用

    flex是词法分析器 bison是语法分析器 基本原理就是flex解析出token后 让bison来使用 实际上 一般是先编写bison脚本 里面的token就是一个定义 没有实现 里面的yylex也是没有实现 只有定义 为什么先做biso
  • 路由(route) 交换机(switch)简介

    路由 route 1 数据包从源地址到目的地址所经过的路径 由一系列路由节点组成 2 某个路由节点为数据包选择投递方向的选路过程 它是连接因特网中各局域网 广域网的设备 一 工作原理 工作于OSI七层协议中的第三层 接收来自一个网络接口的数
  • 租赁行业如何将电子合同活用起来?

    在租赁行业 采取传统纸质合同签署租赁类合同 需要相关人员全部在场签订 效率低下 随着互联网行业的发展 租房 租车等各种租赁类业务逐渐迁移到了线上 互联网租赁平台也因此应运而生 但也带来了各种问题 身份核实困难 中介越过平台做私单 诈骗租赁物
  • 线性代数 --- LU分解(Gauss消元法的矩阵表示)

    Gauss消元法等价于把系数矩阵A分解成两个三角矩阵L和U的乘法 首先 LU分解实际上就是用矩阵的形式来记录的高斯消元的过程 其中 对矩阵A进行高斯消元后的结果为矩阵U 是LU分解后的两个三角矩阵中其中之一 U是一个上三角矩阵 U就是上三角
  • elasticsearch 5.x删除index/type

    elasticsearch 5 x删除index 在head插件中执行 DELETE ip port index 看到 acknowledge true 即为成功 elasticsearch 5 x删除type 在kibana界面 dev
  • java 参数明明名字都是对的为什么值传不过来

    java 参数明明名字都是对的为什么值传不过来 那是因为值的类型不一样 导致匹配不上 一个是long 一个是string
  • C++数据结构笔记(3)线性表的链式存储底层实现

    本系列的帖子并不包含全部的基础知识 只挑一部分最核心的知识点总结 着重于具体的实现细节而并非理论的知识点总结 大家按需阅读学习 链表的核心概念总结如下 1 链式存储不需要连续的内存空间 2 链表由一系列的结点组成 每个节点包含两个域 分别是