【数据结构】顺序表的实现


前言

在这里插入图片描述


一、顺序表的分类

顺序表和数组的区别
顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝
顺序表分为两类:
1.静态顺序表

typedef int SLDataTyep;
typedef struct SqList
{
	SLDataTyep arr[MAX];//定长数组
	int size;//数组长度
}SL;

这样就是顺序表的静态状态,但是他有个缺点就是数组的长度有限给多了就浪费空间,给少了,空间就不够用如果用在用户的信息储存方面的话,如果空间给少了就会造成用户的信息丢失,造成严重的后果,所以我们通常都会用动态顺序表。
2.动态顺序表

typedef int SLDataTyep;
typedef struct SqList
{
	SLDataTyep* arr;
	int size;//有效数据
	int capacity;//空间容量
}SL;

动态顺序表与静态顺序表的区别就是:静态顺序表不需要申请空间,他是已经给好了空间的大小。然而动态顺序表这需要申请空间。
接下来我们将会从顺序表的创建顺序表,插入,删除,查找四个方面来学习动态顺序表

二、顺序表的创建

typedef int SLDataTyep;
typedef struct SqList
{
	SLDataTyep* arr;
	int size;//有效数据
	int capacity;//空间容量
}SL;
//typedef struct SqList SL;这样命名与结构体后的SL一样

这里用typedef int SLDataTyep的好处就是可以帮助我们在以后修改代码的时候更方便。这里的意思就是SLDataTyepint 是一样的效果。他的好处就是如果以后我们想把int 改为其他类型的时候。只把int 改成要改的类型就行。到时候就不用去代码里面一行一行的改了。大大的提高了效率。

1.顺序表的初始化

void  SLIntit(SL* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

顺序表的初始化还是挺简单的。以上就是代码。初始化嘛,就相当于给他赋一个值嘛。我这里只是习惯把他设为0.这里是可以改的哦。设为你喜欢的值就行了,这个要看个人习惯

2.顺序表的销毁

代码如下(示例):

void SLDestory(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

在这里需要注意些什么?其实呀,大多数的 小伙伴在这里把顺序表销毁用free函数销毁就ok。但是,这里一定不要忘记还要给成员变量赋为0哦。

三、 顺序表的插入

顺序表的插入呢一共三种:尾插,头插,在指定位置之前插入数据

1.尾插

在这里插入图片描述
我们来看看这张图。如果我们现在想插入一个数字6,这个就是数字插入6时个个变量的变化图,由此可以写出代码

void SLPushBank(SL* ps,SLDataTyep x)
{
	assert(ps);
	ps->arr[ps->size]=x;
	++ps->size;
}

有此图可以我们还可以思考一个问题。当我们的这个有效数据个数(size)和空间容量大小(capacity)一样大时,当这时我们还要插入一个数时,那该怎么办呢?其实呀很简单,我们这时在给他申请一个数据空间就ok了,那该这样申请呢?请看代码:

void newcapacity(SL* ps)
{
	assert(ps);
	if (ps->capacity == ps->size)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataTyep* tmp = (SLDataTyep*)realloc(ps->arr, newcapacity * sizeof(SLDataTyep));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
}

我们这里用的时realloc来申请空间。这里的realloc具有增容的效果,时按倍数来增容的。所以用realloca比较好一点。
所以正确的尾插代码就是:

void SLPushBank(SL* ps,SLDataTyep x)
{
	assert(ps);
	newcapacity(ps);
	ps->arr[ps->size]=x;
	++ps->size;
}

大家有没有发现他其实就是调用了一次申请空间的函数?是的,就是这样的。

2.头插

我们可以从这张图可以了解到头插是怎样完成
在这里插入图片描述

void SLPushFront(SL* ps, SLDataTyep x)
{
	assert(ps);
	newcapacity(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//arr[1]=arr[0]
	}
	ps->arr[0] = x;
	ps->size++;
}

但是这里要注意呀,千万不要把图中的第4步当成第1步哦。因为这样是不行,如果这样的话,那就会导致数据的丢失哦,因为这样的话第一个数据会替代第二个数据的哦

3.在指定位置之前插入数据

怎样可以实现在指定的位置之前插入数据呢?我们先来画图理解一下:
在这里插入图片描述

可以看出我们这里引入了一个新的变量pos,他的表达的是数组的下标位置 。由图我们可以看出我们在指定位置的下标处插入一个x,那么在pos到size之间的数据都要往后面移动一个单位。这里不要忘记szie要自加哦。
我们来看看代码的实现:

void SLInsert(SL* ps, int pos, SLDataTyep x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLnewcapacity(ps);
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//ps->arr[pos+1]=ps->arr[pos]
	}
	ps->arr[pos] = x;
	++ps->size;
}

这里我们需要注意些什么呢?1:注意pos的范围取值。2:size要自加。

四 、顺序表的删除

顺序表的 删除呢其实也有三种:尾删,头删,删除指定位置数据

1.尾删

我们先来看看图:
在这里插入图片描述
这里szie从1到2的过程中,就直接把数据4给删除了当然我们这里可以给他赋值-1,也可以删掉数据的,只不过用第一种方法要简便一点。我们来看代码:

void SLPopBank(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//ps->arr[ps->size - 1] = -1;
	--ps->size;
}

2.头删

在这里插入图片描述
这里跟头插有点像,只不过这里是除了第一个数据之外,其他数据全部都向前移动一个单位。

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

那为什么这里的 i < ps->szie-1呢?其实从图中我们可以看出来,因为他实质是从arr[ps->size-1]开始的。所以这里是szie-1.

3.删除指定位置数据

我们在解决代码问题的时候可以尝试画图解决,这样会比你直接想效率更高。而且也锻炼了你的动手能力。
我们现在来看看图像:
在这里插入图片描述
从图中可以看出我们想删除下标为2的数据(3)。只需要把4和5向前移动一步就ok了。这点需要注意的是 :是4向前移动,而不是5哦。我们来看看如何实现代码:

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

我们这里的i为什么要小于ps->size-1.那是因为ps->arr[size-1]是数组的最后一个数据,那么他要给给前面一个数据。意思就是说ps->arr [ps->size -2]=ps->arr [ps->size -1],那我们的i最后一次就是size-2,所以这里是size-1.

五 、查找数据

其实查找数据很简单,就一个循环就可以搞定了。

void SLFind(SL* ps, SLDataTyep x)
{
	assert(ps);
	for (int i = 0; i < ps->size ; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;//-1表示无效的下标
}

六、顺序表的整体代码展现与运行效果

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataTyep;
typedef struct SqList
{
	SLDataTyep* arr;
	int size;//有效数据
	int capacity;//空间容量
}SL;
//typedef struct SqList SL;这样命名与结构体后的SL一样
void  SLIntit(SL* ps)//初始化
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
void SLDestory(SL* ps)//顺序表的销毁
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

void SLnewcapacity(SL* ps)//申请空间
{
	if (ps->capacity == ps->size)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataTyep* tmp = (SLDataTyep*)realloc(ps->arr, newcapacity * sizeof(SLDataTyep));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
}
void SLPushBank(SL* ps,SLDataTyep x)//尾插
{
	assert(ps);
	SLnewcapacity(ps);
	ps->arr[ps->size] = x;
	++ps->size;
	/*ps->arr[ps->size]=x;
	++ps->size;*/

}
void SLPushFront(SL* ps, SLDataTyep x)//头插
{
	assert(ps);
	SLnewcapacity(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//arr[1]=arr[0]
	}
	ps->arr[0] = x;
	ps->size++;
}
void SLPopBank(SL* ps)//尾删
{
	assert(ps);
	assert(ps->size);
	//ps->arr[ps->size - 1] = -1;
	--ps->size;
}
void SLPopFront(SL* ps)//头删
{
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	--ps->size;
}
void SLPrint(SL s)//打印
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);
	}
}
void SLInsert(SL* ps, int pos, SLDataTyep x)//在指定位置插入数据
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLnewcapacity(ps);
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	++ps->size;

}
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;
}
int  SLFind(SL* ps, SLDataTyep x)//查找数据
{
	assert(ps);
	for (int i = 0; i < ps->size ; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;//-1表示无效的下标
}
int main()
{
	SL sl;
	SLIntit(&sl);
	printf("尾插\n");
	SLPushBank(&sl, 1);
	SLPushBank(&sl, 2);
	SLPushBank(&sl, 3);
	SLPushBank(&sl, 4);
	SLPrint(sl);
	printf("\n");
	printf("头插\n");
	SLPushFront(&sl, 9);
	SLPushFront(&sl, 8);
	SLPrint(sl);
	printf("\n");
	printf("指定位置之前插入数据:\n");
	SLInsert(&sl, 2, 99);
	SLPrint(sl);
	printf("\n");
	printf("在指定位置删除数据:\n");
	SLErase(&sl, 2);
	SLPrint(sl);
	printf("\n");
	printf("尾删\n");
	SLPopBank(&sl);
	SLPopBank(&sl);
	SLPrint(sl);
	printf("\n");
	printf("头删\n");
	SLPopFront(&sl);
	SLPopFront(&sl);
	SLPrint(sl);
	printf("\n");
	int ret = SLFind(&sl, 2);
	if (ret)
	{
		printf("找到啦,下标是%d\n", ret);
	}
	else
	{
		printf("没有找到\n");
	}
	SLDestory(&sl);
	
	return 0;
}

这是代码的运行结果:
在这里插入图片描述
大家这里可以看见我们有些代码的函数里面有一些有assert(ps),其实这是用来判断ps是否为空的,如果为空就会报错。而还可以给你指出你出错的地方。用上断言呢,这样会使你的代码更健壮。


总结

在这里插入图片描述

我们这里不光要学习顺序表的相关知识,而且还要锻炼自己的画图水平,这样在你解决代码问题时可以给你带来很大帮助。今天就到这里把。要加油哟!各位。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值