数据结构与顺序表:高效数据管理秘籍

hello,各位小伙伴们大家好!!!

目录

数据结构

什么是数据结构?

数据是一个很大的范围。我们在定义的一些变量,a,b,c,d等于1,2,3,4,那1,2,3,4就是数据。再比如学校的教务系统,存储了大家的个人信息,比如说姓名,年龄,学历等等

再比如说我们打开一个网页,在这个网页上我们可以看到图片,文字,一些输入框,一些视频等等,我们把它称之为数据 ,比如说新闻数据一条一条在这里,有格式的摆放,为什么不是这一条那一条,因为我们要把数据管理起来,管理起来方便我们去查询以及一些增删查改操作,这就是我们的数据,所以这些数据我们定义为数据结构 ,它就是由数据和结构两个词组成的
数据结构就是计算机存储和组织数据的一个方式

那比如说,我们同学由各个班级的,A班,B班,C班,我们把班级看成一个结构 ,它把同学们管理起来,我现在要找小李同学,我知道小李同学在B班,我就不会去其他班级去找,我先找B班,再找小李同学,找的就比较快了,而不是大海捞针
数组是最基础的数据结构;比如说:我要存储100个整形数据,如果大家没有学过数组,那我们能想到的是创建100个整型变量。假如说:今后我想从100个变量里面任意去做一些修改呀,查询呀,增加呀,删除呀等等操作,那这个时候我去管理起来是非常复杂的,而且100个变量,如果我想让格式更好看一些,我可能会创建100行,声明我还要初始化,那涉及到的行数可能就会更多,一旦我们有了数组这样的一个数据结构,就可以通过一个数组去维护100个同类型的数据 ,甚至是上千上万个同类型的数据,那通过数组,有了数组这样的一个结构之后,我可以通过下标对指定的数据区增加删除或者是修改,这样的一个数据结构是不是将我们同类型的数据管理起来,所以这里的话数据结构它要围绕数据+管理两个方向去阐述


顺序表

顺序表底层就是数组,也即是说顺序表,它就是在数组的基础上进行了一个维护和封装,在数组的基础上提供了很多的方法,它摇身一变变成了顺序表

有的人会问,为什么要有顺序表?底层已经是数组,数组都已经很好的去管理数据了

注意:当我们使用数组,数组这样的一个结构的时候,比如说今后,我突然想要去修改某一个数据,我现在有一个数组int arr[100],可能我会用到指定的地方post这个位置我给它修改为x,或者插入一个数据,删除一个数据。

假如说,我们这个数组有100个空间,但我数组里面此时只存储5个数据,这个时候,我要在这个数组里面存储一个数据,插入一个新的数据,那我们往哪里插?我们自然能够想到从数组下标为5这个位置,插入一个新的数据,但大家有没有想过这个是我一眼就能够看出来它里面已经有五个数据了,现在,我要往这个位置插入一个新的数据,那它的下标肯定是5 ,是我们肉眼看到的,但是在我们的程序运行过程中,我可能需要一直往这个数组里面去插入数据,一直插的话,也就意味着我要找到当前数组里面已经存储的多少个数据,存储多少个数据,我就在对应的下标去插入一个新的数据,那这里就会涉及到从循环的代码,叫做我们先去找找数字中已经存储了多少个数据,数组中已有元素个数,再插入数据,所以这里的话我要去找已有元素个数,那这里我要去遍历数组,然后统计它已经有多少个元素,再在指定的位置插入数据,那假如说我要删除最后一个元素,那我要去找数组中已有的元素,然后再删除最后一个数据,这个步骤使得代码就没有这么简单了 ,而是我们要写循环,然后插入再去删除,后续我要去写代码,可能会涉及到非常多的数组,这里面每个数组我都需要设计到对应的增加删除查改的操作,如果说每个方法我都需要一次一次去实现,那是不是非常麻烦

所以我们顺序表就发现了这个问题,数组那不行呀,那只是存储数据,但我涉及到一些特殊的此操作的时候你就有点麻烦了,能不能简单点?

顺序表说:虽然我底层是数组,但是我提供了很多现成的方法,开箱即用,我就变成了一个新的很厉害的数据结构。
顺序表是线性表的一种

数据表是某一类具有相同特性的数据结构的集合,分为物理结构和逻辑结构

比如说数组arr有10个空间,这十个空间是连续的,那每个空间它都有自己的地址,我们称为物理距离,物理地址或物理内存 ,所以物理结构实际上指的是数据在内存中存储的时候,它的结构是否是物理的,而在线性表里面物理结构不一定连续,逻辑结构上,如果是连续的,我们称为是线性表。逻辑结构就是我们肉眼看到的,或者是我们想象出来的数据结构他是一个线性的 。比如商店排队买东西,是一个一字型,我们称之为线性。数组就个线性的,我们存储的1,2,3,4, 那我们把它称为一条线,数组的逻辑结构上也是线性的,因为这里每个单元在物理结构上是连续的(在首元素地址上加1,访问第二个元素,加2,访问第三个元素等等)
顺序表的特性: 物理结构上连续,逻辑结构一定是连续的(顺序表底层就是数组)

静态顺序表缺陷:空间给少了不够⽤,给多了造成空间浪费

动态顺序表灵活,能够变通

我们更倾向于动态顺序表

*** 顺序表
存储结构 顺序存储 ,一段连续的内存空间
访问方式 随机访问
查找元素 按值查找
插入/删除 平均需要移动大量元素
空间开销 可静态开辟也可动态开辟,空间利用率高,只存数据
时间复杂度 访问:O(1) 。插入/删除(尾部):O(1) 。插入/删除(中间/头部):O(N) 。查找(无序):O(N)
优点 支持高速随机访问,内存连续,缓存友好,访问效率高
缺点 扩容成本高 ;必须预知或估计容量,可能浪费或不足;插入删除需移动元素,效率低
使用场景 频繁查询,元素数量稳定,需快速随机访问的场景

链表能做的事,顺序表都可以完成,只是操作方法不同,效率不同
链表的插入和删除不是所有情况下都比顺序表快 ,比如尾插尾删,顺序表的时间复杂度为O(1),并且如果是单链表,如果要在中间某个节点的前面插入/删除一个节点,则需要遍历。所以时间的快慢要分情况看待。


代码实现

SeqList.h头文件

首先:定义顺序表的结构

c 复制代码
//静态顺序表的结构
#define N 100
struct SeqList
{
	int arr[N];
	int size;
};
//不推荐使用

//动态顺序表
typedef int SLDataType;//方便后续类型的替换
typedef struct SeqList
{
	SLDataType* arr;//指针
	int size;//size表示顺序表已经存储的数据个数
	int capacity;//顺序表申请的空间个数
}SL;
//我们要用顺序表的话,
// 每次去创建结构体变量的话,加上SeqList非常麻烦
//struct SeqList sl
//我们可以给当前结构体命名
//typedef struct SeqList  SL;
//这里有两种命名方式

函数声明:

c 复制代码
//顺序表初始化
//声明变量
void SLInit(SL* ps);
//顺序表的销毁
void SLDestroy(SL* ps);
void SLCheckCapacity(SL* ps);
//从顺序表的尾部去插入
void SLPushBack(SL* ps, SLDataType x);
//从顺序表的头部去插入
void SLPushFront(SL* ps, SLDataType x);
//顺序表的打印
void SLprint(SL s);
//尾部删除
void SLPopBack(SL* ps);
//头部删除
void SLPopFront(SL* ps);
//在指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//删除指定位置的数据
void SLErase(SL* ps, int pos);
//顺序表的查找
int SLFind(SL* ps, SLDataType  x);

SeqList.c实现文件

初始化

c 复制代码
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
	//结构体指针要用->解引用,不能用.
}

销毁

c 复制代码
void SLDestroy(SL* ps)
{
	if (ps->arr)//申请的有空间
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;//在使用的过程中可能会变成其它的整数,也要销毁
}

尾插

c 复制代码
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	//检查是否为空指针
	//if (ps == NULL)
	//{
	//	return;
	//}
	//或者
	assert(ps);//等价于assert(ps!=NULL)
	SLCheckCapacity(ps);
	//ps->arr[ps->size] = x;
	//++ps->size;
	//或者
	ps->arr[ps->size++] = x;
}

扩容

c 复制代码
void SLCheckCapacity(SL* ps)
{
	//插入数据之前,先看空间够不够,再去插入数据
	//空间不够时:size==capacity
	//size==0,这个是都没申请空间
	if (ps->size == ps->capacity)
	{
		//申请空间
		// 可以在初始化的时候就去申请空间
		//问题:要申请多大的空间/一次增容增多少?
		//增容通常来说是成倍数的增加,一般是2或者3倍
		//若要频繁的增容,则会造成程序运行效率大大降低
		//两倍两倍去增,性能更好
		//空间大小和数据个数成正比
		//判断ps->capacity是不是0,用三目操作符
		//注意:int newCapacity是个整数
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		//但是动态申请空间可能失败
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);//直接退出程序,不再继续执行
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

头插

c 复制代码
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	//运用for循环
	int i = 0;
	for (i = ps->size; i > 0;i--)
	{
		ps->arr[i] = ps->arr[i-1];//arr[1]=arr[0]
	}
	ps->arr[0] = x;
	ps->size++;
}

尾删

c 复制代码
void SLPopBack(SL* ps)
{
	assert(ps);
	//顺序表是否为空,为空不能执行删除操作
	//检验方法如下:
	assert(ps->size);
	//可省略,画蛇添足ps->arr[ps->size - 1] = -1;
	--ps->size;
}

头删

c 复制代码
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//数据整体往前挪动一位
	for (int i = 0; i<ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
		//ps->arr[size - 2] = ps->arr[size - 1];
	}
	ps->size--;
}

在指定位置之前插入数据

c 复制代码
void SLInsert(SL* ps, int pos, SLDataType x)
{
//size为有效个数
//pos是顺序表对应的下标,pos必须是大于等于零的数据
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	//插入数据:空间够不够
	SLCheckCapacity(ps);
	//让pos及之后的数据整体往后挪动一位
	for (int i = ps->size; i>pos; i--)
	{
	//易错,细细琢磨
		ps->arr[i] = ps->arr[i - 1];
		//arr[pos+1]=arr[pos]
	}
	ps->arr[pos] = x;
	ps->size++;
}

删除指定位置的数据

c 复制代码
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];
		//arr[size-2]=arr[size-1];
	}
	ps->size--;
}

打印

c 复制代码
void SLprint(SL s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);//注意这种打印方式
	}
	printf("\n");
}

顺序表的查找

需遍历一遍,一个一个排查,进行查找

c 复制代码
int SLFind(SL* ps, SLDataType  x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
  {
		if (ps->arr[i] == x)
		{
			//找到了
		  return i;
		}
  }
	//没有找到
   return -1;
}

测试

c 复制代码
void SLTest01()
{
	SL sl;//不要忘记这个步骤
	SLInit(&sl);
	//中间执行一些增删查改操作
	//插入数据之前,要保证有空间去插入数据
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLprint(sl);
	SLDestroy(&sl);
}
c 复制代码
void SLTest02()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLprint(sl);//1 2 3 4
	//测试顺序表的查找
	int find = SLFind(&sl, 4);
	if (find < 0)
	{
		printf("没有找到!\n");
	}
	else
	{
		printf("找到了!小标为%d\n",find );
	}
	SLDestroy(&sl);
}


本次内容就到这里,如果对你有帮助,记得点赞三联一波哦!!!

相关推荐
星火开发设计2 小时前
共用体 union:节省内存的特殊数据类型
java·开发语言·数据库·c++·算法·内存
求梦8202 小时前
【力扣hot100题】合并两个有序链表(22)
算法·leetcode·链表
dcmfxvr2 小时前
adwawd
算法
啊阿狸不会拉杆2 小时前
《数字信号处理 》第 7 章-无限长单位冲激响应 (IIR) 数字滤波器设计方法
数据结构·算法·信号处理·数字信号处理·dsp
IT_Octopus2 小时前
力扣热题100 20. 有效的括号
算法·leetcode
木井巳2 小时前
【递归算法】求根节点到叶节点数字之和
java·算法·leetcode·深度优先
想进个大厂2 小时前
代码随想录day29 贪心03
算法·leetcode·职场和发展
踩坑记录3 小时前
leetcode hot100 环形链表 easy 快慢指针
leetcode·链表
We་ct3 小时前
LeetCode 15. 三数之和:排序+双指针解法全解析
前端·算法·leetcode·typescript