顺序表详解

目录

初始化顺序表

销毁顺序表

打印顺序表

增加数据

头插

尾插

指定下标位置插入


顺序表首先是线性表,而线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。

简单来说,顺序表是用一段物理地址连续的存储单位,依次存储数据元素的线性结构,一般情况下采用数组存储在数据上,完成数据的增删查改。

 那顺序表有分两种

1.静态顺序表:使用定长数组存储元素

 2.动态顺序表:使用动态开辟的数据存储,可以利用相关的接口函数来实现数据的增删查改。

在这里,我们可能会有些迷惑,顺序表和普通的数组有什么区别呢?
顺序表和普通数组的区别主要有以下两点:

1.顺序表的长度可以动态增长,普通数组的长度是固定的。
2.顺序表要求插入的数据在内存中是连续的,普通数组的数据存放可以不连续。

 接下来我们来实现动态顺序表的各个接口函数

初始化顺序表

首先,我们要创建一个顺序表类型,该顺序表类型包括了顺序表的起始位置(指针指向的首元素地址)、记录顺序表内已有元素个数的计数器(size),以及记录当前顺序表的容量的变量(capacity)。

typedef int SLDataType;//重命名让你可以随便修改顺序表中数据的类型

typedef struct SeqList
{
	SLDataType* a; //声明了一个指向顺序表的指针,姑且称它为“顺序表指针”
	int size; //记录当前顺序表内元素个数
	int capacity; //记录当前顺序表的最大容量
}SeqList;

然后,我们需要一个初始化函数,对顺序表进行初始化。

//初始化顺序表
void SeqListInit(SeqList* ps)
{
	assert(ps);   //防止传入的指针为空
	ps->a = NULL; //刚开始时顺序表为空,顺序表指针为NULL
	ps->size = 0; //起始时元素个数为0
	ps->capacity = 0;//容量为0
}

销毁顺序表

因为顺序表所用的内存空间是动态开辟在堆区的,所以我们在使用完后需要及时对其进行释放,避免造成内存泄漏

//销毁顺序表
void SeqListDestory(SeqList* ps)
{
	assert(ps);
	free(ps->a);//释放顺序表指针指向的空间
	ps->a = NULL;//及时置空
	ps->size = 0;//元素个数置0
	ps->capacity = 0;//容量置0
}

打印顺序表

打印就比较简单了,依次循环打印size个元素即可。

//打印顺序表
void SeqListPrint(SeqList* ps)
{
	assert(ps);
	int i = 0;
	//循环打印顺序表指针指向的数据
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

增加数据

仔细想想,我们每次需要增加数据的时候,首先都应该先检查顺序表内元素个数是否已达顺序表容量上限。若已达上限,那么我们就需要先对顺序表进行扩容,然后才能增加数据

当然,增加数据还分两种,在头部插入数据还是在尾部插入数据(头插和尾插)

不管头插尾插都要检查容量是否满,我们再封装一个扩容函数

//检查顺序表容量是否已满,若已满,则增容
void SeqCheckCapacity(SeqList* ps)
{
	if (ps->size == ps->capacity)//满了,需要增容
	{
		//判断顺序表容量是否为0,若为0,则先开辟用于存放4个元素的空间大小,若不为0,则扩之前的两倍
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* newA = realloc(ps->a, newcapacity*sizeof(SLDataType));
		if (newA == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = newA;//开辟成功,将顺序表指针更新
		ps->capacity = newcapacity;//容量更新
	}
}

注意:若传入realloc的指针为空指针(NULL),则realloc函数的作用相当于malloc函数。

头插

要想在顺序表的表头插入数据,那么就需要先将顺序表原有的数据从后往前依次向后挪动一位,最后再将数据插入表头。

//头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
	assert(ps);
	SeqCheckCapacity(ps);//检查容量
	int i = 0;
	for (i = ps->size; i > 0; i--)//将数据从后往前依次向后挪
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;
	ps->size++;//顺序表元素个数加一
}

 注意以上代码再插入数据前有挪数据的行为,这个是头插的麻烦之处,效率很低,而接下来的尾插没有这样的烦恼

尾插

尾插相对于头插就比较简单了,直接在表尾插入数据即可。

//尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
	assert(ps);
	SeqCheckCapacity(ps);//检查容量
	ps->a[ps->size] = x;
	ps->size++;//顺序表元素个数加一
}

指定下标位置插入

要做到在指定下标位置插入数据,首先我们需要得到一个下标位置,然后从该下标位置开始(包括该位置),其后的数据从后往前依次向后挪动一位,最后将数据插入到该下标位置。

//指定下标位置插入
void SeqListInsert(SeqList* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);//检查输入下标的合法性
	SeqCheckCapacity(ps);//检查容量
	int i = 0;
	for (i = ps->size; i > pos; i--)//从pos下标位置开始,其后的数据从后往前依次向后挪
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;
	ps->size++;//顺序表元素个数加一
}

我们可以发现,头插和尾插实际上就是在下标为0的位置和下标为ps->size的位置插入数据,也就意味着我们可以统一使用该函数来实现头插和尾插。(复用插入的函数!)

//头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
	SeqListInsert(ps, 0, x);//在下标为0的位置插入数据
}
//尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
	SeqListInsert(ps, ps->size, x);//在下标为ps->size的位置插入数据
}

删除数据

删除数据,其实可以理解为:从某个位置开始,数据依次向前覆盖。这样一来,该位置的数据就相当于删除了。当然顺序表为空你就不能删了,这里如何判断顺序表是否为空,有两个方法,一个温柔的:写一个if语句来判断,不会报错,然后打印一些提示语句。还有暴力的:写assert语句判断是否为空,为空直接报错,编译不通过,其实暴力的方法就是把锅甩给用函数的人,告诉你代码用错了,顺序表为空怎么还要删!(当然暴力的方法我觉得更好)

头删

要删除表头的数据,我们可以从下标为1的位置开始,依次将数据向前覆盖即可。

//头删
void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);//保证顺序表不为空
	int i = 0;
	for (i = 0; i < ps->size - 1; i++)//将数据从前往后依次向前覆盖
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;//顺序表元素个数减一
}

注意:数据覆盖的时候应从前向后依次覆盖,若从后向前覆盖,会导致前一个数据被覆盖。

尾删

尾删就更简单了,直接将顺序表的元素个数减一即可。

//尾删
void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);//保证顺序表不为空
	ps->size--;//顺序表元素个数减一
}

指定下标位置删除

要删除指定下标位置的数据,我们只需要从下标位置开始,其后的数据从前向后依次覆盖即可。

//指定下标位置删除
void SeqListErase(SeqList* ps, int pos)
{
	assert(ps);
	assert(ps->size > 0);//保证顺序表不为空
	assert(pos >= 0 && pos < ps->size);
	int i = 0;
	for (i = pos; i < ps->size - 1; i++)//从pos下标位置开始,其后的数据从前往后依次向前覆盖
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;//顺序表元素个数减一
}

同样的道理,头删和尾删实际上也就是删除下标为0的位置和下标为ps->size - 1的位置的数据,也就意味着我们可以统一使用该函数来实现头删和尾删。

//头删
void SeqListPopFront(SeqList* ps)
{
	SeqListErase(ps, 0);//删除下标为0的位置的数据
}
//尾删
void SeqListPopBack(SeqList* ps)
{
	SeqListErase(ps, ps->size - 1);//删除下标为ps->size - 1的位置的数据
}

修改数据

修改数据,就直接对该位置的数据进行再次赋值即可。

//修改指定下标位置元素
void SeqListModify(SeqList* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//检查输入下标的合法性
	ps->a[pos] = x;//修改数据
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值