当前位置: 首页 > news >正文

初阶数据结构(3)顺序表

在这里插入图片描述

Hello~,欢迎大家来到我的博客进行学习!

目录

  • 1.线性表
  • 2.顺序表
    • 2.1 概念与结构
    • 2.2 分类
      • 2.2.1 静态顺序表
      • 2.2.2 动态顺序表
    • 2.3 动态顺序表的实现
      • 初始化
      • 尾插
      • 头插
      • 尾删
      • 头删
      • 查找
      • 指定位置之前插入数据
      • 删除指定位置的数据
      • 销毁

1.线性表

首先我们需要知道的是,顺序表和链表都属于线性表。线性表是具有相同特性的一类数据结构的集合。

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

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

这里的相同特性我从逻辑结构和物理结构两个方面进行分析。

  • 逻辑结构(一定是线性的):人为想象出来的。
  • 物理结构(不一定是线性的):比如数组在存储空间是连续的,和物理结构上的线性是一样的。

2.顺序表

2.1 概念与结构

概念:顺序表是用⼀段物理地址连续的存储单元(物理结构是线性的)依次存储数据元素的线性结构,⼀般情况下采用数组存储。顺序表的底层结构是数组。
在这里插入图片描述
顺序表和数组的区别?
顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。
在这里插入图片描述

2.2 分类

2.2.1 静态顺序表

如果顺序表底层是固定的空间大小,我们把它叫做静态顺序表。
实现静态顺序表,需要三个文件:头文件(.h)、源文件(.c)、测试文件(test.c)。

  • 头文件(.h):用来声明一些结构和我们的方法
  • 源文件(.c):方法的具体实现
  • 测试文件(test.c):用于测试

取名为SeqList(Sequence List),意为连续的。
我们可以定义一下静态顺序表的大小N,还需要一个size来定义一下有效数据的个数。同时这里的数据类型可能是各种各样的,使用typedef来解决。当要使用这个结构体的时候,我每次都要加上关键词,感觉麻烦,使用typedef来解决。

SeqList.h

#define N 1000
typedef char SLDataType;
//静态顺序表
typedef struct SeqList {
	SLDataType arr[N];
	int size;//有效数据的个数
}SL;

2.2.2 动态顺序表

如果空间是不固定的,我想要多少,可以去增加,就是动态顺序表。

#define N 1000
typedef char SLDataType;
//动态顺序表
typedef struct SeqList {
	SLDataType* arr;
	int size;//有效数据的个数
	int capacity;//容量大小
}SL;

2.3 动态顺序表的实现

初始化

现在我先进行初始化,参数传一个s,初始化的具体方法在SeqList.c文件中实现。
SeqList.c

#include"SeqList.h"
//初始化
void SLInit(SL s)
{
	s.arr = NULL;
	s.size = s.capacity = 0;
}

在test.c中进行检验。

#include"SeqList.h"
void SLTest()
{
	SL sl;
	SLInit(sl);
}

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

此时会报错:
在这里插入图片描述
在test.c的sl是实参,在SeqList里面的s为形参。
在这里插入图片描述
我们需要将sl的地址传过去,而不是传值。传值时,改变形参并不能改变实参。在SeqList.c这里我就应该用指针来接收。
改完以后:
SeqList.c

#include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

test.c

#include"SeqList.h"
void SLTest()
{
	SL sl;
	SLInit(&sl);
}

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

SeqList.h

#include<stdio.h>
#include<stdlib.h>
#define N 1000
typedef int SLDataType;
静态顺序表
//typedef struct SeqList {
//	SLDataType arr[N];
//	int size;//有效数据的个数
//}SL;

//动态顺序表
typedef struct SeqList {
	SLDataType* arr;
	int size;//有效数据的个数
	int capacity;//容量大小
}SL;

//初始化
void SLInit(SL* ps);

调试看看:
在这里插入图片描述
跳出以后,sl里面的部分也初始化了:
在这里插入图片描述

尾插

现在来添加插入数据的功能,顺序表的底层是数组,我可能需要需要在原来数据的末尾、中间、开头插入数据。
首先实现尾插(在顺序表最后一个可插的位置,插入数据)的功能。
在这里插入图片描述
如上图,我有5块空间,里面有3个有效数据。尾插就是在3的后面插入数据。

用SLPushBack这个函数来实现,里面的参数为ps和我们要插入的数据。现在我们需要在顺序表里插入数据,这里有三个成员:arr、size、capacity。size指向的位置刚好就是最后一个有效数据的下一个位置。
在这里插入图片描述
假设空间足够,现在往里面插入一个数据X = 99,直接往3后面放,这里并不需要遍历数据组,往size这个位置放就行,插入完成之后,size++。

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	ps->arr[ps->size] = x;
	++ps->size;
}

假设空间不够(此时size和capacity在一个位置),按以上方式插入数据,会越界。就需要对原数组申请空间,,然后再在size位置插入数据,插入后size++。
因而我们需要分情况来写。
在这里插入图片描述
在增容时,我们使用realloc(可以在原数组的基础上进行增加容量)来进行这一操作。如果插一个数据,增加一个容量,没有空间的浪费,但是增容频繁,程序效率低下。如果一次多申请一些,可能会造成空间的浪费。通常,增容是按倍数增加,如2倍、3倍…这里我选择两倍。
在这里插入图片描述
按照以上思路,其实是有漏洞的。

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	//空间不够,申请空间
	if (ps->size == ps->capacity)
	{
		//空间不够,2倍增容
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, ps->capacity * 2);


	}
	ps->arr[ps->size++] = x;
}

在realloc这里,第二个参数是size_t size单位是字节,ps->capacity * 2 这里改为 ps->capacity * 2 *sizeof(SLDataType)。并看看增容成功没有。但是之前我们初始化那里把capacity初始化为0,现在需要扩容的话,需要先给一个初始值。我这里先给一个SLDestroy函数,在程序结束时释放动态分配的内存,不然程序结束时会出现内存泄漏。
SeqList.c

#include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	if (ps == NULL)
	{
		return;
	}

	//空间不够,申请空间
	if (ps->size == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//空间不够,2倍增容
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
	ps->arr[ps->size++] = x;
}

// 释放顺序表
void SLDestroy(SL* ps) 
{
	if (ps->arr != NULL) 
	{
		free(ps->arr);  // Free the allocated memory
		ps->arr = NULL;
	}
}

在这里插入图片描述

头插

现在来实现头插。这里也要看空间大小够不够,则可以分装一个方法来判断空间大小够不够。前面的步骤和尾插差不多,只是实现头插时有部分区别。需要把数据整体往后移动,然后再进行插入。在移动的时候,先把后面的数据往后移动,最后把插入的数据放在下标为0的位置。同样在增加完数据之后,size++。
SeqList.c

#include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

void SLcheckCapacity(SL* ps)
{
	//空间不够,申请空间
	if (ps->size == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//空间不够,2倍增容
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	if (ps == NULL)
	{
		return;
	}

	SLcheckCapacity(ps);

	ps->arr[ps->size++] = x;
}

//头插
void SLPushFront(SL* ps, SLDataType x)
{
	if (ps == NULL)
	{
		return;
	}
	SLcheckCapacity(ps);
	//直接头插
	//数据整体向后移动一位
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	++ps->size;
}

尾删

ps不能传空,删除数据之后,size要 - -。顺序表为空,也不行。这里把最后一个数据弄掉,不是把最后一个数据弄为0,而是可以修改

//尾删
void SLPopBack(SL* ps)
{
	assert(ps&&ps->size>0);

	--ps->size;

}

头删

这里的前提和尾删一样。ps不能传空,删除数据之后,size要 - -。顺序表为空,也不行。我们需要移动数据,下标为零以后的数据,整体向前移动一位

//头删
void SLPopFront(SL* ps)
{
	assert(ps && ps->size > 0);

	--ps->size;
	for (int i = 0; i<ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
}

查找

这里比较简单,遍历顺序表,如果查找到返回下标;如果找不到返回无效下标。

//查找
int SLFind(SL* ps, SLDataType x)
{
	for(int i = 0; i < ps->size; i++)
	{
		if (x == ps->arr[i])
		{
			//找到了,返回下标
			return i;
		}
	}
	//找不到
	return -1;
}

指定位置之前插入数据

这里新增一个参数pos(指定位置)。pos需要有效,pos >= 0 && pos <= ps->size。与之前一样需要判断空间是否足够,插入好之后size++。
假设需要将99这个数据插入3这个位置之前,需要将pos以及之后的数据向后移动一位。
在这里插入图片描述

//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	//前=:头插
	//后=:尾插
	assert(pos >= 0 && pos <= ps->size);
	SLcheckCapacity(ps);
	for (int i = ps->size - 1; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	++ps->size;
}

删除指定位置的数据

同样的size需要减减,指定位置pos必须pos >= 0 && pos <= ps->size。要删除pos位置的数据就需要把pos之后的数据整体向前移。

//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	for (int i = pos;i<ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}

	--ps->size;
}

销毁

动态申请的空间在不用的时候需要销毁。

//销毁
void SLDestroy(SL* ps)
{
	if (ps->arr)
		free(ps->arr);
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

此时,我们对顺序表已经比较理解了。

好了,我们的顺序表的知识就讲到这里。如果文章内容有误,请大佬在评论区斧正!谢谢大家!
在这里插入图片描述

相关文章:

  • Electron使用WebAssembly实现CRC-32 STM32校验
  • 爱普生高精度车规晶振助力激光雷达自动驾驶
  • Python基础知识点(函数2)
  • 自用记录 | AI辅助 在线画图工具 使用Mermaid语法(流程图 ER图)
  • 【Kafka基础】Kafka 2.8以下版本的安装与配置指南:传统ZooKeeper依赖版详解
  • 如何拿到iframe中嵌入的游戏数据
  • 2023年蓝桥杯第十四届CC++大学B组真题及代码
  • Linux内核设计——(二)进程调度
  • CMake实战指南一:add_custom_command
  • 手撕算法——宽度优先搜索-BFS
  • Shell脚本编程之正则表达式
  • JS DOM节点增删改查
  • Spring事务传播机制
  • 算法(动态规划)
  • elasticsearch索引数据备份与恢复
  • Python基于OpenCV和SVM实现中文车牌识别系统GUI界面
  • 【STL 之速通pair vector list stack queue set map 】
  • Linux系统学习Day04 阻塞特性,文件状态及文件夹查询
  • LeetCode 416、606题解(中等dp、回溯)
  • FPGA_DDR(一) 仿真
  • 网站建设好销售吗/关键词检测
  • 做网站找公司怎么找/网页制作费用大概多少
  • 真分析对比他们的功能及特点_提出自己对政府门户网站建设的见解./青岛seo精灵
  • 电商货源网站/今日国内新闻最新消息大事
  • 上海外贸网站seo/重大军事新闻
  • 网站建设net接口/线上电商怎么做