C语言版本数据结构03:顺序表+链表

2023-11-11

今天我们来学习数据结构的第一个顺序结构:顺序表和链表。

1.线性表

线性表是我们要学的第一个结构,我们知道一串连续的数字或者字符想要在内存中存储可以用数组,但是我们又知道数组是静态的,那么如果我想要对这组数据进行增删查改呢?这就显现出线性表的意义了,我们先来看一下线性表的定义吧。

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结 构,常见的线性表:顺序表、链表、栈、队列、字符串...

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物 理上存储时,通常以数组和链式结构的形式存储。

 那么线性表总共分为两类:一个是顺序结构的线性表(顺序表),所谓有顺序也就是我们常说的数组的形式,是用一段物理地址连续的存储单元依次存储数据元素的线性结构;还有一种是链式结构的线性表(链表),每个存储数据的节点额外还要有一个指针来存储此节点的下一个,那么我来用图片的方式帮助大家理解。

 

2.顺序表

概念讲解

讲完了它们的基本结构,那我们该如何实现一个顺序表结构呢?它应该包含哪些元素呢?我们来梳理一下,首先一个数组是必不可少的,其次我们还需要一个变量来存储数组的大小,有了这些我们就可以利用结构体将这两个元素合成为一个顺序表的结构。

//静态顺序表
#define N 10
typedef int SLDataType;
//将数据类型定义成一个新类型,这样如果以后想改只改这里就好
typedef struct SeqList
{
    SLDataType arr[N];
    size_t size;       
}SL;//结构体重命名

我们可以看到一个静态的顺序表就初始化好了,为什么叫静态呢?因为我们宏定义了N为10,这样当我们用SL创建了一个变量,它再内存中就会像是这样:

因为是静态结构,所以只能存储10个数据,如果有更多的数据那只能更改宏定义的N了,但是顺序表有静态的就有动态的,我们再来看看动态的顺序表是如何定义的吧:

typedef int SLDataType;
typedef struct SeqList
{
    SLDataType* a;
    size_t size;
    size_t capacity;
}SL;

这样以来的话虽然直比上面的静态结构多了一个变量,但是顺序表做到了动态增长,按需索取,如果存储数据到达了capacity的极限,我们只需要调用动态内存开辟函数来继续为我们开辟空间。

接口实现

大家可能对接口二字比较陌生,所谓接口也可以理解为我们自己封装的函数,那么一个顺序表结构需要哪些接口呢?(以动态顺序表为例,静态顺序表有局限,所以我们只做了解)

typedef int SLDataType;
// 顺序表的动态存储
typedef struct SeqList
{
     SLDataType* array; // 指向动态开辟的数组
     size_t size ; // 有效数据个数
     size_t capicity ; // 容量空间的大小
}SeqList;
// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl, size_t capacity);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x); 
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 顺序表打印
void SeqListPrint(SeqList* psl);

我们看到对于一个完整的顺序表来说最基本的接口就这些,初始化销毁必不可少,而对于数据我们必须要有增删查改的功能,还有打印功能,删除指定位置的值,返回指定位置的值,在某位置删除,某位置插入值等等,那我们就来依次完成这接口吧。

void SeqListInit(SL* ps)//初始化函数
{
	ps->a = NULL;//将a指向的空间设为空,代表对于a的初始化
	ps->size = 0;//size和capacity都设置为0,
	ps->capacity = 0;
}
void SeqListDestory(SL* ps)//销毁函数
{
	free(ps->a);//释放a指向的空间,并且size和capacity都置0
	ps->size = 0;
	ps->capacity = 0;
}
void SeqListCheckCapacity(SL* ps)//检查内存函数
{
	if (ps->size == ps->capacity)
	{
		//没有空间or空间满了
		//扩容
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * (sizeof(SLDataType)));//内存开辟函数,当没有开辟内存时,malloc和realloc功能一样
		if (tmp == NULL)//如果开辟失败,要给予提醒
		{
			//realloc失败
			printf("realloc fail");
			exit(-1);
		}
		//扩容成功
		ps->a = tmp;//将新的空间赋值给a
		ps->capacity = newcapacity;//赋值新的内存
	}
}
void SeqListInsert(SL* ps, int pos, SLDataType x)//插入数据函数
{
	assert(pos <= ps->size && pos >= 0);//首先判断ps不可为空
	//if (pos > ps->size || pos < 0)
	//{
	//	printf("pos invalid");
	//	return;
	//}
	SeqListCheckCapacity(ps);//检查内存,如果内存不够用及时扩容
	int end = ps->size - 1;//对于数组来说,最后一个元素的下标是整体大小的-1
	//挪数据
	while (end >= pos)//在某处插入数据后其后面的数据都要向后移动
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;//插入数据
	ps->size++;//调整大小
}
void SeqListErase(SL* ps, int pos)//删除数据函数
{
	assert(pos < ps->size&& pos >= 0);
	int begin = pos + 1;//用一个变量来保存要删除位置的后一个
	while (begin < ps->size)//删除掉该元素后其后面的元素都要向前挪动一个
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;//调整空间
}
void SeqListPushBack(SL* ps, SLDataType x)//尾插
{
	SeqListCheckCapacity(ps);

	//ps->a[ps->size] = x;
	//ps->size++;
	SeqListInsert(ps, ps->size, x);
    //直接调用Insert函数即可,由于尾插只能在最后一位插入,所以pos传入ps->size即可
}
void SeqListPopBack(SL* ps)//尾删
{
    assert(ps->size > 0);
	//if (ps->size > 0)
	//{
	//	//ps->a[ps->size - 1] = 0;
	//	ps->size--;
	//}
	//ps->size--;
	SeqListErase(ps, ps->size-1);//直接调用Erase函数
}
void SeqListPushFront(SL* ps, SLDataType x)//头插
{
	SeqListCheckCapacity(ps);//插入前先要判断内存,没有内存的话要及时增容
	挪数据
	//int end = ps->size - 1;
	//while (end >= 0)
	//{
	//	ps->a[end + 1] = ps->a[end];
	//	end--;
	//}
	//ps->a[0] = x;
	//ps->size++;
	SeqListInsert(ps, ps->size, x);
}
void SeqListPopFront(SL* ps)//头删
{
	assert(ps->size > 0);
	//int begin = 0;
	//while (begin < ps->size - 1)
	//{
	//	ps->a[begin] = ps->a[begin + 1];
	//	begin++;
	//}
	//ps->size--;
	SeqListErase(ps, 0);
}
int SeqListFind(SL* ps, SLDataType x)//找出数据下标的函数
{
	assert(ps->size > 0);
	while (ps->size--)
	{
		if (ps->a[ps->size] == x)
		{
			return x;
		}
	}
	return -1;
}
void SeqListPrint(SL* ps)//打印函数
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

实现了这些接口后,相信大家已经对顺序表已经有了一个大概的了解,接下来有几个问题需要大家考虑:

1. 中间/头部的插入删除,时间复杂度为O(N) 2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们 再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

想过这些问题后,大家有没有觉得其实顺序表虽说在某些方面是有它的优势,但是也有一定的缺陷,那么我们就引出我们今天的第二个结构:链表,我们一起来看看链表是如何实现的,再来比较一下链表和顺序表他们的优劣。

3.链表

链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

如我们上面画的图一般,链表之所以叫做链表是因为它不同于顺序表的是它是一个个结点链接而成的,而链接它们的就是后一个结点的地址,我们知道其实在物理层面它们并没有被链接在一起,根本没有这个“链子”,这只是我们想出来的,我们可以拿现实生活中的火车来理解这一例子。

链表的分类

我们所画的呢,是一种用处非常广的单链表,我们学习数据结构最先接触的也是单链表,但是我想告诉大家的是链表其实有很多形式:单链表,双向链表,带头结点的单链表双链表等,循环单链表,循环双向链表等等。 

有这么多的形式,我们用的最多的还是无头不循环单链表和带头循环双向链表,取了两个极端,这样的话链表的所有功能我们都可以实现了。

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结 构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。 2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向 循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而 简单了,后面我们代码实现了就知道了。

链表的实现

typedef int SLDataType;

typedef struct SListNode
{
	SLDataType data;//存数据
	SLTNode* next;//存下一个结点的地址
}SLTNode;

//单链表要实现的接口
void SListPrint(SLTNode* phead);
void SListInit(SLTNode* phead);
void SListPushBack(SLTNode** pphead, SLDataType x);
void SListPopback(SLTNode** pphead);
void SListPushFront(SLTNode** pphead, SLDataType x);
void SListPopFront(SLTNode** pphead);
SLTNode* SListFind(SLTNode* phead,SLDataType x);
void SListInsert(SLTNode* phead,SLTNode* pos,SLDataType x);
void SListErase(SLTNode** pphead, SLTNode* pos); SLTNode* SListFind(SLTNode* phead, SLDataType x);
void SListDestory(SLTNode* phead);

实现的话就留给大家自己实现了,可以按照上面顺序表的方式来完成这些接口的实现。

双向链表的结构也给大家

typedef int LTDataType;
typedef struct ListNode
{
 LTDataType _data;
 struct ListNode* next;
 struct ListNode* prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* plist);
// 双向链表打印
void ListPrint(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* plist);
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* plist);
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

4.链表和顺序表的区别

我们学完链表后,我们来看看它们之间有什么区别吧

 好了,关于顺序表和链表的知识就到这里,我们下次再见。

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

C语言版本数据结构03:顺序表+链表 的相关文章

  • 【华为OD机试】数字最低位排序【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 给定一个非空数组 列表 起元素数据类型为整型 请按照数组元素十进制最低位从小到大进行排序 十进制最低位相同的元素 相对位置保持不变 当数组元素为负值时 十进制最低为等
  • 集合引用类型 下

    目录 Map Map set Map get Map delete Map has Map values Map entries Map clear 选择Object 还是Map 数据转换 转为数组 转为 JSON 对象转为 Map 数组转
  • 【Matlab】系统的响应分析

    前言 一个信号系统课程中使用Matlab对系统的零状态响应 零输入响应 完全响应 冲激响应 阶跃响应求解 波形生成以及分析的实验 一 内容 设系统的微分方程为 激励为 起始状态条件为 可求得 零输入响应 零状态响应 完全响应 冲激响应 阶跃
  • 攻防世界web新手题解题writeup

    攻防世界web新手题 1 view source 题目描述 X老师让小宁同学查看一个网页的源代码 但小宁同学发现鼠标右键好像不管用了 题目场景 http 220 249 52 133 58537初级题 按下F12查看网页源码得到flag 2
  • vue自定义指令实现复制功能

    思路 使用浏览器自带的execCommand使用Copy 但此方法只能是被选中的值才能进行复制粘贴 动态创建一个文本域 将拿到的文字放在文本域中 然后自动选中 再调用浏览器方法即可 提示 想要选中文本框的内容 有如下两个方法可以 方法一 通
  • 用python画一束满天星花朵,python满天星绘制流程图

    大家好 小编来为大家解答以下问题 用python画一束满天星花朵 python满天星绘制流程图 今天让我们一起来看看吧 1 用python画一百个同心圆的代码 import matplotlib pyplot as plt from mat

随机推荐

  • u盘文件删除如何恢复呢?

    相信很多上班族都有自己的u盘 u盘是用来储存一些我们可以随身携带的数据的 无论是学习还是工作 只要用电脑 都需要使用u盘 而u盘就是一个小容器 可以装一些重要的文件 方便我们随身携带 当u盘里的文件在使用u盘的过程中不小心被删除了 怎么恢复
  • selenium的安装及使用介绍

    R爬虫之上市公司公告批量下载 selenium的安装及使用介绍 http yphuang github io blog 2016 03 01 Get Listed Company Announcement
  • 【超简单】使用TensorFlow训练和部署图像分类模型

    一 简介 使用TensorFlow和OpenCV实现图像分类 按照流程图运行程序 可以非常简便地完成图像预处理 生成数据集 训练模型和部署模型 四个程序的功能如下 preprocess py 对图像进行重命名和调整大小 建议拍摄形状为正方形
  • 数据流图学习

    数据流图或数据流程图 Data Flow Diagram 缩写为DFD 是什么 数据流图是结构化分析方法中使用的工具 它以图形的方式描绘数据在系统中流动和处理的过程 由于它只反映系统必须完成的逻辑功能 所以它是一种功能模型 标志了一个系统的
  • JavaScript中apply()和call()的区别和应用

    JavaScript中的每一个Function对象都有一个apply 方法和一个call 方法 这两个方法能够改变函数体内部 this 的指向 例如 fun1 call 或者fun1 apply 都是为了改变fun1函数内部的this指向
  • Java 实现 AES 对称加密算法的加解密

    Java 实现 AES 对称加密算法的加解密 前言 一 对称加密算法简介 1 对称加密 2 加密模式 3 填充模式 二 AES 加解密代码实例 1 生成 AES 密钥 2 AES 加解密 3 AES nonce 加解密 前言 文章字数比较多
  • uniapp 里折叠面板嵌套 uni-collapse 高度不能自适应解决办法

  • python:根据一个列表对另外一个列表排序

    在使用python处理数据时可能会遇到根据列表A对列表B进行排序的问题 记录一下想到的两个方法 方法1 根据列表b中每个元素的下标来获取列表a中对应位置的元素 将其作为排序依据即可 import random a x for x in ra
  • JVM你知道多少?

    1 jvm的内存结构 方法区和堆是所有线程共享的内存区域 而java栈 本地方法栈和程序计数器是运行时线程私有的内存区域 1 Java堆 Heap 是Java虚拟机所管理的内存中最大的一块 Java堆是被所有线程共享的一块内存区域 在虚拟机
  • naive bayes java_Naive Bayes 朴素贝叶斯的JAVA代码实现

    最进写毕业论文找了下贝页斯的资料 这个文章讲的很好 转载下 链接http blog csdn net michael kong nju article details 12623557 comments 1 关于贝叶斯分类 bayes 是一
  • STM32—ADC详解入门(ADC读取烟雾传感器的值)

    目录 一 ADC是什么 二 ADC的性能指标 三 ADC特性 四 ADC通道 五 ADC转换顺序 六 ADC触发方式 七 ADC转化时间 八 ADC转化模式 九 实验 使用ADC读取烟雾传感器的值 1 配置 2 代码 一 ADC是什么 AD
  • MSYS2更换国内源

    今天安装了Msys64 但是Msys64使用的国外源实在太慢 必须更新为国内源 目前测试过国内最快是清华大学的源 我的安装路径为d msys64 为什么要安装在D盘 因为msys64需要不断更新数据和安装自己的软件 也就是说会占用越来越多的
  • 使用mnist数据集实现手写字体的识别

    1 MNIST是一个入门级的计算机视觉数据集 它包含各种手写数字图片 它也包含每一张图片对应的标签 告诉我们这个是数字几 该数据集包括60000行的训练数据集 mnist train 和10000行的测试数据集 mnist test 每一张
  • mysql索引左侧原则,你真的了解吗?

    前言 写这篇文章源自一位杠精同事提了个问题 左侧原则跟where条件顺序有无关系 我想了想 好像是有关系的 不敢确定 但是自己又懒得动手测试 于是发起ETC自动抬杠功能 强行杠了一拨 结果杠输了 接下来即是动手验证 预习执行计划 实践 咱们
  • Something about C

    What the hell How long since I touch with C What a pity I have to work with it now Global variable Better define a globa
  • c++中setw()与setfill()的用法详情

    c 中setw 与setfill 的用法详情 在C 中 setw int n 用来控制输出间隔 例如 cout lt lt s lt
  • 关于自搭网站XAMPP(三)MYSQL增删改查(水)

  • Ubuntu 18.04下载安装以及分区教程

    收藏一下写的超赞的博客 Ubuntu18 04安装教程 超详细的图文教程 安装Ubuntu Linux系统时硬盘分区最合理的方法ubuntu18 04分区设置 也安装过很多次ubuntu了 记录一下最重要的踩坑点 分区完成后会让选择安装启动
  • 算法之字符串匹配一

    目录 前言 BF算法 RK算法 总结 参考资料 前言 字符串匹配指的是一个短点的字符串与一个长点的字符串进行匹配 并确认短的字符串是否在长的字符串中存在 匹配算法有很多 本文介绍两种简单 容易理解的两种算法 BF算法和RK算法 BF算法 B
  • C语言版本数据结构03:顺序表+链表

    今天我们来学习数据结构的第一个顺序结构 顺序表和链表 1 线性表 线性表是我们要学的第一个结构 我们知道一串连续的数字或者字符想要在内存中存储可以用数组 但是我们又知道数组是静态的 那么如果我想要对这组数据进行增删查改呢 这就显现出线性表的