2024年最全【神秘海域】[动图] 顺序表千字破解~_神秘海域顺序(3),2024年最新从草根到百万年薪程序员的十年风雨之路

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

结构的实现代码为:

typedef int SLDataType;

typedef struct SeqList
{
	SLDataType\* arr;    // 指向动态开辟的数组
	int Size;    //数组内存放数据数量
	int Capacity;    //顺序表容量
}SeqList;
// 此结构的顺序表 创建后,使用前,需要初始化

顺序表接口实现🐬

静态顺序表 只适用于确定知道需要存多少数据的场景
所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小

以下也都是以动态顺序表为基础

一个动态顺序表一般拥有这些接口:

  1. 初始化 seqListInit
  2. 容量检查 checkCapacity
  3. 尾插 seqListPushBack
  4. 尾删 seqListPopBack
  5. 头插 seqListPushFront
  6. 头删 seqListPopFront
  7. 查找 seqListFind
  8. 指定位置插入 seqListInsert
  9. 指定位置删除 seqListErase
  10. 销毁顺序表 seqListDestory
  11. 打印顺序表 seqListPrint

下面就来一一实现这些接口:
🌈

顺序表动态存储结构🐟
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType\* arr;    // 指向动态开辟的数组
	int Size;    //数组内存放数据数量
	int Capacity;    //顺序表容量
}SeqList;
// 此结构的顺序表 创建后,使用前,需要初始化

实现动态顺序表结构,需要在结构体内定义一个 某数据类型(需存放的数据类型)的 数组。剩下的操作全部由接口函数实现。
🌈

顺序表初始化🐟
// 顺序表初始化
void seqListInit(SeqList* psl); 

初始化函数非常的简单,只需要将创建好的顺序表结构体内:
arr 指向 NULL
SizeCapacity 赋予 0
就完成了一个顺序表的初始化

实现代码:

// 顺序表初始化
void seqListInit(SeqList\* psl)
{
	assert(psl);

	psl->arr = NULL;
	psl->Size = psl->Capacity = 0;
}

创建一个顺序表验证一下:

创建出的顺序表,初始化成功。
🌈

顺序表尾插 及 容量检查🐟
// 顺序表容量检查
void checkCapacity(SeqList* psl);
// 顺序表尾插
void seqListPushBack(SeqList* psl, SLDataType x);

顺序表尾插,即 在顺序表最后一个数据之后存放数据
存放数据进入顺序表之前,要 先确保顺序表处于未满状态 的,不然数据无法存储。

所以在实现尾插之前,先 实现一个容量检查的函数
容量检查一般需要实现什么功能?

  1. 检查顺序表是否已满
  2. 如果顺序表已满,则扩容

具体实现如下:

void checkCapacity(SeqList\* psl)
{
	assert(psl);

	if(psl->Size == psl->Capacity)
	{// 如果存入的数据数量 与 顺序表的容量相等,就代表顺序表已满
		int newCapacity = psl->Capacity == 0 ? 2 : 2 \* psl->Capacity;    
		//如果当前容量等于0,代表新顺序表,新容量给 2
		//如果当前容量不等于0,代表非新顺序表,新容量给原来容量的两倍
		SLDataType\* tmp = (SLDataType\*)realloc(psl->arr, sizeof(SLDataType)\* newCapacity);
		// 先用tmp 指向 realloc 扩容出新空间,以防止扩容失败导致元数据丢失
		if(tmp == NULL)
		{// tmp 为空 表示扩容失败,退出程序
			printf("realloc fail!\n");
			exit(-1);
		}
		// tmp 不为空,再将tmp 赋于 psl->arr;
		psl->arr = tmp;  //psl->arr 指向扩容后的地址
		psl->Capacity = newCapacity;   // 容量改为扩容后的容量
	}
}

容量检查函数实现成功之后,就可以继续 实现尾插函数

先考虑一下,尾插都需要注意哪些事项

  1. 需要先用容量检查函数检查容量
  2. 需要在数组中 Size 的位置放入数据之后,Size 自增
    (因为数组下标从 0 开始,所以 Size 位置即为最后一个数据之后)

具体实现如下:

// 尾插
void seqListPushBack(SeqList\* psl, SLDataType x)
{
	assert(psl);    //断言保证传入的结构体的地址不为空

	checkCapacity(psl);    //容量检查函数检查容量

	psl->arr[psl->Size] = x;    // 将 x 尾插
	psl->Size++;    // 存储数据量加1 , Size也要加1
}

每实现一个接口,最好都要验证一下

尾插三个整型数据,验证

尾插三次,扩容两次,尾插成功.
🌈

顺序表打印🐟
// 顺序表打印
void seqListPrint(SeqList* psl);

尾插存放数据是可以了,但是如何实现顺序表的打印呢?

顺序表打印 接口的实现也是非常的简单:

// 顺序表打印
void seqListPrint(SeqList\* psl)
{
	assert(psl);

	for (int i = 0; i < psl->Size; i++)
	{
		printf("%d ", psl->arr[i]);
	}
	printf("\n");
}

验证:

🌈

顺序表尾删🐟
// 顺序表尾删
void seqListPopBack(SeqList* psl);

实现了顺序表的尾插,如果需要删除数据,对应的还有顺序表的尾删。

尾删需要注意几个点:

  1. 尾删需不需要将 需要删除的数据 的空间释放掉?
  2. 尾删需不需要将 需要删除的数据 赋值为 0 ?
  3. 尾删需不需要控制一下顺序表中的 Size
  4. 控制不控制 Size 的值,对顺序表来说有什么区别?

先来思考一下第一个问题,尾删需不需要将 需要删除的数据 的空间释放掉?
答案是,不需要,也无法释放。
因为 free 函数使用的条件是,mallocrealloc 函数开辟的整块空间,且传入的必须是是这块空间的首地址。所以无法释放其中的单独一块空间。

第二个问题,尾删需不需要将 需要删除的数据 赋值为 0 ?

答案同样是,不需要。
因为尾删之后,下次使用这块空间一定是在插入数据的时候,到时候会有新的数据将其覆盖,所以不需要将其赋值为 0`

第三个问题和第四个问题,一起思考一下:

尾删需不需要控制一下顺序表中的 Size
控制不控制 Size 的值,对顺序表来说有什么区别?

答案是,需要控制 Size 的值,防止 过多次使用尾删(使用次数比存放数据数量多) 导致 Size 变成负数。
Size 变成负数,会发生什么情况?

使用下面段 尾删函数试验一下:

// 顺序表尾删
void seqListPopBack(SeqList\* psl)
{
	assert(psl);

	psl->Size--;
}


尾插 3 个数据,但是尾删了 5 次
可以看到,顺序表中的 Size 变成了 -3
变成负数之后,如果继续进行其他操作,一定会发生错误
比如:
即使,尾插了两次,顺序表中还是无法输出数据,也就是说,再次尾插的两个数据并没有存放至顺序表中。
因为 这两次尾插,是从 Size 为 -3 的地方执行的,并没有从 0 位置开始,所以无法存入,同时也发生了越界现象

这里 VS 编译器 不报错,是因为 对于数组越界的情况, VS编译器 是抽查的。也就是说,并不是所有的越界情况 VS编译器 都能检查得到

所以尾删,需要对顺序表中的 Size 进行一个简单的控制:

// 顺序表尾删
void seqListPopBack(SeqList\* psl)
{
	assert(psl);
	
	if (psl->Size > 0)
	{// 当Size 大于零 再进行自减
		psl->Size--;
	}
}

这样就不会发生使 Size 减到负 的问题:

即使 尾删次数过多,Size 也不会变为负数,也就不会发生其他操作错误的情况。
🌈

顺序表头插🐟
// 顺序表头插
void seqListPushFront(SeqList* psl, SLDataType x);

顺序表的尾插比较简单,只需要在顺序表 Size 位置放入数据就可以了。

头插呢?头插需要注意到什么问题?
顺序表本质上是 数组实现的,那么需要在数组的首位置插入数据就不仅仅是插入数据那么简单。需要先将数组中的数据向后移动一位,然后才能将新数据放入首位置
演示:

那么来实现头插一下:

// 头插
void seqListPushFront(SeqList\* psl, SLDataType x)
{
	assert(psl);    //断言保证传入的结构体的地址不为空

	checkCapacity(psl);    //插入前 首先检查容量

	// 方法一:
	int end = psl->Size;
	while (end)
	{
		psl->arr[end] = psl->arr[end - 1];
		end--;
	}

	// 方法二:
	/\*int end = psl->Size - 1;
 while (end >= 0)
 {
 psl->arr[end + 1] = psl->arr[end];
 end--;
 }\*/

	// 这两个方法其实一样的,只是对 末尾元素的位置控制方式不同
	

	psl->arr[0] = x;    // 将 x 尾插
	psl->Size++;    // 存储数据量加1 , Size 加1
}

验证:

头插成功。
🌈

顺序表头删🐟
// 顺序表头删
void seqListPopFront(SeqList* psl);

实现了头插,接着来实现一下头删

头删和头插的思路相似,不过只是将从 1 ~ Size-1 位置的数据向前移动一位。
不过,头删同样需要保证 Size > 0 才能执行。
演示:

头删实现:

// 顺序表头删
void seqListPopFront(SeqList\* psl)
{
	assert(psl);

	if (psl->Size > 0)
	{
		int begin = 1;
		while (begin < psl->Size)
		{
			psl->arr[begin] = psl->arr[begin - 1];
			begin++;
		}

		psl->Size--;
	}
}

验证:

即使删除过多,也不会出错。
🌈

顺序表查找🐟
int seqListFind(SeqList* psl, SLDataType x);

查找顺序表中某个数据所在的位置,可以用到顺序表查找的操作。
因为顺序表的本质是数组,所以 只需要将顺序表从头到尾遍历一遍,找到值就返回 其所在的位置 ,找不到就返回 -1

查找的实现:

// 顺序表查找
int seqListFind(SeqList\* psl, SLDataType x)
{
	assert(psl);

	for (int i = 0; i < psl->Size; i++)
	{
		if (x == psl->arr[i])
			return i;
	}

	return -1;
}

验证:

验证了,一般情况、边界情况、未找到情况,均返回正确。
🌈

指定位置插入🐟
void seqListInsert(SeqList* psl, size_t pos, SLDataType x);

尾插、头插都实现了,接下来就实现一下指定 pos 位置插入数据。
在指定 pos 位置插入数据,和前插有一点相似,需要将数据向后移一位。

我们可以用相似的方法移动,不过是从 pos 位置以后的数据向后移动一位。
不过首先还是要检查容量:

// 指定位置插入
void seqListInsert(SeqList\* psl, size\_t pos, SLDataType x)
{
	assert(psl);
	
	SeqListCheckCapacity(psl);    // 检查容量
	
	size\_t end = psl->Size;
	while(end > pos)
	{
		psl->arr[end] = psl->arr[end - 1];
		end--;
	}
	
	psl->arr[pos] = x;
	psl->Size++;
}

验证一下:

在插入之前可以使用 查找函数 获取一下 pos 位置


pos 位置插入数据 成功了。

但是真的成功了吗?
这个函数,现在还有没有什么问题?
比如,当我在 20 这个位置插入,会发生什么呢?
看一下:

20 位置插入数据没有报错,但是输出的时候输出的是随机值
而且,在实际的操作中,程序是延迟了一会才结束的
为什么?

因为,顺序表中存放的数据数量只有 9 个,占用的位置只有0 ~ 8,如果在 20 位置插入数据,超出了顺序表应有的位置,这个时候 Size 照常自增
那么输出的时候就会输出 0 ~ 9 位置的数据,而 9 位置并没有赋予数据,所以输出的是随机值。

这就提出了一个问题:当传入的 pos 位置比实际应该插入的位置大,怎么解决?
其实也很简单,如果 pos 位置过大,直接结束 插入函数 就可以了。

那么改进后的代码就是:

// 指定位置插入
void seqListInsert(SeqList\* psl, size\_t pos, SLDataType x)
{
	assert(psl);
	
	checkCapacity(psl);    // 检查容量
	
	if(pos > psl->Size)
	{// 尾插的位置是 Size,如果大于Size,就代表pos过大
		printf("Insert fail. Pos > Size! Pos = %d \n", pos);


![img](https://img-blog.csdnimg.cn/img_convert/c165fd6fec7600ca7cace4c538d931e6.png)
![img](https://img-blog.csdnimg.cn/img_convert/74aa1a9a3038479af698193f93319e3e.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

`pos` 位置过大,`直接结束 插入函数` 就可以了。


那么改进后的代码就是:



// 指定位置插入
void seqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
assert(psl);

checkCapacity(psl);    // 检查容量

if(pos > psl->Size)
{// 尾插的位置是 Size,如果大于Size,就代表pos过大
	printf("Insert fail. Pos > Size! Pos = %d \n", pos);

[外链图片转存中…(img-izFLbgyF-1715758114370)]
[外链图片转存中…(img-0Nq9D8FZ-1715758114371)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值