数据结构第一弹--realloc开辟动态顺序表讲解,代码实现与调试(画图解读)


前言

线性表是在实际中广泛应用的数据结构.常见的线性表有:顺序表,链表,队列等等.
线性表在逻辑上宿舍连续的,我们可以认为是连续的直线.但是线性表在内存存储中不一定是连续存放的.比如链表.也有可能是连续存放的–顺序表.
内存存储是否连续各有优劣,没有高下之分

一、顺序表是什么?

简单来说,顺序表就是数组.但是和数组有些不同,数组存放数据可以说随机的,而顺序表按顺序进行存放.
顺序表是线性表的一种,线性表是n个具有相同特性的元素组合成的有限序列 (比如int型组成int类型的数组).线性表在逻辑上面是连续存放的,但是在物理结构上面却不一定是连续的.
顺序表在物理结构上是连续存放的,而且必须是按顺序进行存放.

二.静态顺序表和动态顺序表的优劣对比

静态顺序表

#define N 7
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType a[N];//定长数组
	int size;//有效个数
}SL;

在外面的课本上面多数举得案例都是静态数组.为什么呢?因为它相对于动态顺序表更好写一点
但是它的缺点也很明确:
我们虽然可以通过define定义的宏变量来修改定长数组的值,但是每次写完数组的长度就固定了.除非你明确知道要存放的数据 的大小,不然开辟少了不够,开多了浪费.

动态顺序表

typedef int SLDataType;//要修改数据类型只需要修改这一行

typedef struct SeqList
{
	SLDataType* a;//指向将要进行动态开辟的数组
	int size;//当前存放的有效个数
	int capacity;//当前的容量
}SL;

因为我们要存放的数据类型不一定是int型,也有可能是double甚至是指针.
所以我们把类型进行重定义.以后要进行数据类型的修改只需要改一行就行

因为数组是动态开辟的,所以它会有一个变量来显示当前的容量,如果不够就变大.动态顺序表比静态顺序表就好得不是一星半点了,它极大地降低了内存的浪费,让数据存储更省心.

三、动态顺序表实现步骤

1.初始化顺序表

typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* a;//指向将要进行动态开辟的数组
	int size;//当前存放的有效个数
	int capacity;//当前的容量
}SL;

先把结构体变量的值全部置为0
assert是判断pl指针是否为空.

void SeqListInit(SL* pl)
{
	assert(pl);//不能对空指针进行解引用操作,防止传过来的是空指针

	pl->a = NULL;//将要进行动态开辟的数组
	pl->capacity = 0;//顺序表的容量置为0
	pl->size = 0;//有效个数置为0
}

调用方式

void Test1()
{
	SL sl;//定义一个结构体变量
	SeqListInit(&sl);//传地址才会修改它的值
}
int main()
{
	Test1();

	return 0;
}

这里注意要传给SeqListInit()函数的是结构体变量sl的地址.
只有通过sl的地址才能修改它的值

2.尾插数据

在尾插数据之前,大家有没有发现上面不对劲?
我们要动态开辟的a数组面前为空
而且因为我们是动态开辟的,所以后面我们每次存储都要检查当前容量是否能够放下数据

void SLCheckCapacity(SL* pl)
{
	assert(pl);

	if (pl->size == pl->capacity)
	{
		int newcapacity = pl->capacity == 0 ? 4 : pl->capacity * 2;
		//如果容量是0就改成4,不然就在本来的基础上翻倍

		SLDataType* tmp = (SLDataType*)realloc(pl->a,sizeof(SLDataType)*newcapacity);
		//当传给realloc的指针为空时,它相当于malloc
		if (tmp == NULL)
		{
			perror("realloc fail");
			//当realloc开辟失败后会传会空指针
			exit(-1);//退出程序
		}
		pl->a = tmp;
		pl->capacity = newcapacity;//覆盖旧容量
	}
}

sizeof(SLDataType)newcapacity
类型的大小
新的容量

SLDataType* tmp相当于给pl->a加了一道保险,
因为realloc开辟失败后会传会空指针,要是直接将realloc开辟出来的指针传给pl->a会出大问题.
在这里插入图片描述
size当前的位置为空,所以直接将x的值赋给size.

void SeqListPushBack(SL* pl, SLDataType x)
{
	assert(pl);
	SLCheckCapacity(pl);
	
	pl->a[pl->size] = x;
	pl->size++;//插入数据size要变大
}

3.打印顺序表

努力了怎么久,总得听个响

void SeqListPrint(SL* pl)
{
	assert(pl);
	for (int i = 0; i < pl->size; i++)
	{
		printf("%d ", pl->a[i]);//打印有效数据
	}
	printf("\n");
}

按顺序打印顺序表a存放的值
测试:

void Test1()
{
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 6);
	SeqListPushBack(&sl, 7);
	SeqListPushBack(&sl, 9);
	SeqListPrint(&sl);
}
int main()
{
	Test1();

	return 0;
}

在这里插入图片描述

4.头插数据

在这里插入图片描述
将begin的值放到size-1处
将begin的值向后面覆盖
当begin=-1时循环结束
在这里插入图片描述
给起始位置腾出空间来插入数据

void SeqListPushFront(SL* pl, SLDataType x)
{
	assert(pl);
	SLCheckCapacity(pl);//检查容量
	int end = pl->size - 1;
	while (end >= 0)
	{
		pl->a[end + 1] = pl->a[end];
		end--;
	}
	pl->a[0] = x;
	pl->size++;
}

调试


void Test1()
{
	SL sl;
	SeqListInit(&sl);
	SeqListPushFront(&sl, 99);
	SeqListPushFront(&sl, 98);
	SeqListPushFront(&sl, 97);
	SeqListPushFront(&sl, 96);
	SeqListPrint(&sl);
}
int main()
{
	Test1();

	return 0;
}

在这里插入图片描述

5.尾删数据

我们不需要修改末尾位置的值,直接让size–,减少有效数据的个数,从而使打印顺序表的函数无法访问该数据.

void SeqListPopBack(SL* pl)
{
	assert(pl);
	assert(pl->size>0);//size为0就不能继续删了

	pl->size--;
}

注意
size为0不能继续删,否则在后面free进行释放时会报错.

6.头删数据

对size的大小进行检查,为0不能再删
在这里插入图片描述
设begin=1,将begin所指向的值往前挪动.循环直到begin==size
在这里插入图片描述
然后有效个数size–;

void SeqListPopFront(SL* pl)
{
	assert(pl);
	assert(pl->size > 0);//为0不能删
	int begin = 1;
	while (begin < pl->size)
	{
		pl->a[begin - 1] = pl->a[begin];//不断往前挪动数据
		begin++;
	}

	pl->size--;
}

测试

void Test2()
{
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 6);
	SeqListPushBack(&sl, 7);
	SeqListPushBack(&sl, 9);
	SeqListPrint(&sl);

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);//尾删
	SeqListPrint(&sl);

	SeqListPopFront(&sl);//头删
	SeqListPopFront(&sl);
	SeqListPrint(&sl);
}
int main()
{
	Test2();
	return 0;
}

在这里插入图片描述

7.任意位置插入数据

因为顺序表是连续存放的,所以这个任意位置也只是相对而言
对pos的位置进行检查
这个位置不能小于数组的起点,也就是a[0];
也不能大于size(size里面是空的,所以可以插入值)
在这里插入图片描述
将end放到size-1的位置上,再挪动end的值到end+1上面,然后end–.
到pos=end,循环结束.

在这里插入图片描述
给pos挪出空间来存放数据.

oid SeqListInsert(SL* pl, int pos, SLDataType x)
{
	assert(pl);
	assert(pos>=0 &&pos<=pl->size);//检查pos位置是否规范

	SLCheckCapacity(pl);//检查容量

	int end = pl->size - 1;
	while (end >= pos)
	{
		pl->a[end + 1] = pl->a[end];
		end--;
	}
	pl->a[pos] = x;
	pl->size++;//增加和删除都会改变size的值
}

调试

void Test3()
{
	SL sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 6);
	SeqListPushBack(&sl, 7);
	SeqListPrint(&sl);

	SeqListInsert(&sl, 2, 999);//任意位置插入数据
	SeqListInsert(&sl, 0, 333);
	SeqListPrint(&sl);

}
int main()
{
	Test3();

	return 0;
}

在这里插入图片描述

8.任意位置删除数据

因为顺序表是连续存放的,所以这个任意位置也只是相对而言
对pos的位置进行检查
这个位置不能小于数组的起点,也就是a[0];
不能大于或者等于当前的有效存储量size,size当前没有存放值
在这里插入图片描述
pos所在的位置要删除,我们可以把pos+1的值对pos进行覆盖,然后begin++.
在这里插入图片描述
在这里插入图片描述
以此类推,当begin遇到size,循环结束, 覆盖的任务完成.

void SeqListErase(SL* pl, int pos)
{
	assert(pl);
	assert(pos >= 0 && pos < pl->size);//对pos的位置进行检查
	int begin = pos + 1;
	while (begin < pl->size)
	{
		pl->a[begin - 1] = pl->a[begin];
		begin++;
	}
	pl->size--;
}

增加和删除都会改变size的值,而容量capacity的大小只与动态开辟realloc有关.
调试:

void Test3()
{
	SL sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 6);
	SeqListPushBack(&sl, 7);
	SeqListPrint(&sl);

	SeqListErase(&sl, 1);//任意位置删除
	SeqListPrint(&sl);
}
int main()
{
	Test3();

	return 0;
}

在这里插入图片描述

9.销毁顺序表

*首先出于稳妥,我们还是给结构体指针pl进行断言一下

void SeqListDestroy(SL* pl)
{
	assert(pl);
	if(pl->a != NULL)
	{
		free(pl->a);
		pl->a = NULL;

		pl->capacity = 0;//将容量置为0
		pl->size = 0;将大小置为0
	}
}

记得要将pl->a置为NULL,不然会变成野指针

总结

简单来说,顺序表就是一个数据连续存放的数组.
因为顺序表物理存储连续,所以我们对顺序表的访问非常方便,修改数据也相对容易,但数据表也存在问题.
顺序表存在的问题:
1.在我们进行头插/删或者是任意位置插入和删除时,我们都要挪动后面的数据,时间复杂度是O(N)
2.realloc扩容方式有原地扩容和异地扩容.当数组后面的空间足够时,进行原地扩容.相反,当数组后面的空间不够时,会进行异地扩容.
异地扩容:另外找一片地方存储数据,会拷贝之前的数据并对原地址进行释放异地扩容会有不小的消耗.
3.虽然动态顺序表相对于静态顺序表来说,浪费问题降低了不少,当还是存在.假如当前容量为100,而我要存储110个数据,容量满了会扩容道200.会有90个数据空间的浪费.
如何解决上面的问题呢?
数据结构下一弹—链表会给出答案.

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海的宇宙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值