用C语言实现一个顺序表(上)

用C语言实现一个顺序表(上)

一、准备工作

1.何为顺序表?

顺序表是一种存储数据的方式,是用一段物理地址连续的存储单元依次存储数据的线性结构。我们知道,数组是存储于一个连续空间且具有相同数据类型的元素的集合,因此,我们通常在顺序表中使用数组进行数据的存储。通过对数组的一些操作,完成数据的增、删、查、改等等。

2.实现一个顺序表,我们需要些什么?

(1)数组

经过刚才的分析,数组是不可或缺的了,那么,该给数组开多大的空间呢?有一种叫作静态顺序表,在使用之前开辟好一块固定的空间,但前提是你得知道要存多少个数据。否则开多了浪费空间,开少了又不够使用,它不够灵活。因此,我们往往采用动态顺序表:通过动态内存开辟的方式,让数组的容量跟着需求来。

(2)size和capacity

capacity:表示当前数组能够存多少个数据,指的是数组的容量。(注意:这里的容量不是指数组有多少个字节的空间,而是能够存储数据 的个数,因此,我们可以有这样的公式:数组空间的大小(单位:字节)=数组所存储的单个数据大小 * capacity
size:表示当前数组已经存了多少个数据。

当 size =capacity 时,我们就该对数组扩容了。

(3)使用一个结构体

我们现在有了一个数组,size(整型)和capacity(浮点型)。它们虽然数据类型不同,但却紧密联系,共同实现一个顺序表,因此,我们可以将他们放入一个结构体中(如图)。
顺序表的结构体

二、顺序表的实现(代码+分析)

1.顺序表的初始化

结构体SeqList告诉我们实现顺序表需要的一些内容,但是它只是一张图纸,就像房屋的设计图一样,告诉我们哪里是客厅,哪里是厨房,却不能实实在在地住进去。因此,我们需要对顺序表进行初始化(代码如下)。

void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->a = (DataType*)malloc(sizeof(DataType) * 4);//先开辟4个数据的空间    
	if (ps->a == NULL)//基本操作,检查动态内存开辟是否成功
	{
		perror("malloc fail");
		return;
	}
	ps->size = 0;//初始化,还未存入数据,size为0
	ps->capacity = 4;//表示初始化后能存4个数据
}
int main()
{
	SeqList s;
	SeqListInit(&s);

	return 0;
}

void SeqListInit(SeqList* ps)是初始化函数,我们发现这里传的是结构体的指针,而不是结构体本身。为什么呢,因为形参只是实参的一份临时拷贝,出了函数的作用域后就销毁了,形参在函数里再怎么变化对实参都没有什么影响,想要改变实参,应当传它的地址。一个经典的例子就是写一个交换a,b值的函数时,传地址才有效,道理是一样的。

2.顺序表的销毁

顺序表的销毁比较简单,代码如下:

void SeqListDestroy(SeqList* ps)
{
	assert(ps);
	free(ps->a);//释放动态开辟的空间
	ps->a = NULL;	
	ps->size = 0;//存储的有效数据没有了
	ps->capacity = 0;//数组的容量也归零
}

3.顺序表的遍历

对顺序表的遍历其实就是对数组的遍历,往往采用一个循环即可。循环的起始就是数组下标为零的地方,而数组现有数据量为size,那么我们也能够轻松地知道循环的终止条件,代码如下:

void SeqListPrint(SeqList* ps)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

4.顺序表的扩容

动态顺序表的关键特点就是在容量不够时能够进行扩容。
什么时候开始扩容?size和capacity相等时;括多大的空间?根据自己的需求,比如可以两倍扩容;扩容用什么函数?使用realloc函数进行空间大小调整,代码如下:

void CheckCapacity(SeqList* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		DataType* tmp = (DataType*)realloc(ps->a, sizeof(DataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->a = tmp;//tmp指向的是调整后内存的起始地址
		ps->capacity *= 2;//这里采用的是两倍扩容
	}
}

5.数序表数据的尾部插入(尾插)

顺序表的尾插,其实就是在数组的末尾插入数据,我们只要找到数组最后一个数据的下标即可。这里需要注意的是,在插入数据之前,应当检查一下数组的空间是否足够,之前的扩容函数就派上了用场,代码如下:

void SeqListPushBack(SeqList* ps,DataType x)
{
	assert(ps);
	CheckCapacity(ps);//检查是否需要扩容
	ps->a[ps->size] = x;//插入数据
	ps->size++;//有效数据+1
}

6.顺序表的尾部数据删除(尾删)

顺序表的尾删也相对简单,不过在尾删之前要判断一下顺序表里是否还有数据,代码如下:

void SeqListPopBack(SeqList* ps)
{
	if (ps->size == 0)//检查是否存在有效数据
		return;
	else
		ps->size--;//遍历的时候就不会遍历到最后一个元素了
}

7.运行测试

测试代码如下:

int main()
{
	SeqList s;
	SeqListInit(&s);//初始化

	SeqListPushBack(&s, 1);//尾插数据
	SeqListPushBack(&s, 2);
	SeqListPushBack(&s, 3);
	SeqListPushBack(&s, 4);
	SeqListPushBack(&s, 5);

	SeqListPrint(&s);//打印

	SeqListPopBack(&s);
	SeqListPopBack(&s);//连续尾删两次
	SeqListPrint(&s);

	SeqListPopBack(&s);//尾删一次
	SeqListPrint(&s);

	SeqListPopBack(&s);//尾删一次
	SeqListPrint(&s);

	SeqListPopBack(&s);//尾删一次
	
	SeqListPrint(&s);//打印

	SeqListDestroy(&s);//销毁

	return 0;
}

结果如图:
测试结果(上)

三、本期总结+下期预告

本期内容主要是实现一个顺序表的准备工作+顺序表的一些基本操作的实现,不过这只是其中的一部分,下期内容将为大家介绍顺序表的头插,头删以及某个位置数据的插入删除等等。

感谢大家的关注,我们下期再见!
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值