顺序表(C语言实现)

news2024/10/6 5:54:08

 

什么是顺序表

顺序表和数组的区别 

顺序表本质就是数组 

结构体初阶+进阶 系统化的学习-CSDN博客

简单解释一下,就像大家去吃饭,然后左边是苍蝇馆子,右边是修饰过的苍蝇馆子,但是那个好看的苍蝇馆子一看,这不行啊,我这个和你的东西一样名字一样,我这个吸引不来客户,那我改个名字,叫米其林。

就像顺序表和数组之间的区别。

顺序表属于什么

顺序表既是一个数据结构,也是C语言中可以使用的一种数据组织方式。
作为数据结构,顺序表是一种抽象的数据类型,它指定了一种线性数据集合,其中的元素按照一定的顺序排列,并且可以通过索引来访问。顺序表在许多编程语言中都有实现,包括C语言。
在C语言中,顺序表通常通过数组来实现。数组是C语言提供的一种基本数据类型,它可以用来存储一系列相同类型的数据。数组的长度在定义时确定,但C语言也支持动态分配内存的方式来创建可变大小的数组,这相当于实现了动态顺序表。
因此,顺序表作为一个概念,属于数据结构领域;而在C语言中,顺序表可以通过数组或动态分配的数组来实现。 

顺序表的特点

 顺序表是一种线性表,其底层结构是数组。顺序表有以下几个重要的点:
1. 顺序表有两种类型,静态顺序表和动态顺序表。静态顺序表的大小固定,通常使用定长数组实现,适用于数据量已知的情况。动态顺序表可以根据需要动态调整大小,适用于数据量未知或者变化频繁的情况。
2. 顺序表支持随机访问,即可以通过索引直接访问数组中的任意一个元素,时间复杂度为O(1)。
3. 顺序表的插入和删除操作通常在表尾进行,这样不需要移动多余的元素,时间复杂度为O(1)。当表满时,需要先进行扩容操作,扩容的时间复杂度可能为O(n)。
4. 顺序表的所有操作,包括插入、删除、查找、修改等,都需要借助数组实现。在使用顺序表时,需要注意数组的越界问题。
5. 顺序表在存储空间连续的情况下,能够实现较快的数据传输和处理,但在存储空间不连续的情况下,会带来访问速度上的损失。

静态顺序表

静态顺序表就是我给定一个空间,这里可以存放你需要的内容

举个栗子:你想要创建一个图书管理系统,那这个学校一共俩人三人的,那你给定空间就好了,没有必要费劲巴力的写那麽多代码,虽然可能会造成空间的浪费,但是还是比较少的。

但是需要知道的是,一旦超过你需要的空间,就会导致数据的丢失。

静态顺序表适合以下场景:

  1. 数据量已知:当应用程序在设计时已经知道将要存储的数据量,并且这个数据量在运行时不会发生变化时,使用静态顺序表是一个很好的选择。

  2. 数据不经常改变:如果数据集合在大部分时间内保持稳定,只有偶尔的插入或删除操作,静态顺序表可以提供高效的存储和访问。

  3. 内存空间有限:在内存资源有限的环境中,使用静态顺序表可以避免动态内存分配带来的开销,因为静态顺序表在编译时就已经分配了固定大小的内存。

  4. 性能要求高:静态顺序表的访问速度通常比动态顺序表快,因为不需要进行动态内存分配或扩容操作。在性能要求极高的应用中,静态顺序表可能是一个更好的选择。

  5. 简单应用:对于简单的应用程序或教学环境,静态顺序表由于其简单性和易于理解,是一个合适的数据结构选择。

  6. 固定大小的数据集:例如,存储固定数量的已知数据项,如日期、代码表或其他常量集合,这些数据集在运行时不需要扩展。

动态顺序表

动态顺序表那就是这个空间是可以变化的

就像抖音创建账号一样,如果你就给100万个账号的空间,那么静态顺序表,很快就使用完毕了。抖音当时使用的时候也想不到一晚上增容一个亿。所以这个时候我适合使用动态顺序表。

动态顺序表适合以下场景:

  1. 数据量变化:当应用程序需要处理的数据量在运行时可能会发生变化,或者数据量事先未知时,动态顺序表是一个很好的选择。它可以根据需要动态地调整大小,以适应不断变化的数据量。

  2. 数据量未知:在处理大量数据或不确定数据量的情况下,动态顺序表可以开始时分配一个较小的容量,随着数据的增加逐渐扩容,避免一开始就分配过多不必要的内存。

  3. 频繁插入和删除:动态顺序表适合频繁进行插入和删除操作的应用场景。由于它可以在末尾方便地进行这些操作,不需要像数组那样在每次操作时移动大量元素。

  4. 性能要求不是首要考虑:虽然动态顺序表的插入和删除操作在大多数情况下速度很快,但在扩容时可能会涉及到较慢的内存重新分配。如果性能要求不是主要考虑因素,或者可以通过其他方式优化性能,那么动态顺序表是一个合适的选择。

  5. 需要灵活性:在需要根据应用程序的需求灵活调整数据结构大小的场景中,动态顺序表提供了这种灵活性。它可以适应不同规模的数据集,而无需预先定义数据集的大小。

  6. 内存使用不是关键限制:动态顺序表可能会比静态顺序表消耗更多的内存,因为它需要额外的空间来存储指针。如果应用程序的内存使用不是关键限制因素,或者可以通过其他方式优化内存使用,那么动态顺序表是一个合适的选择。

顺序表和线性表

 顺序表是线性表的一种实现方式。线性表是一个具有相同特性的数据元素的有限序列,其中每个元素都仅有一个直接前驱和直接后继。顺序表通过一组地址连续的存储单元依次存储线性表中的各个元素,使得线性表中在逻辑结构上相邻的元素存储在相邻的物理存储单元中。顺序表有两种类型,分别是静态顺序表和动态顺序表,它们之间的主要区别在于内存分配方式不同。静态顺序表使用定长数组存储元素,而动态顺序表则使用动态开辟的数组存储,其大小可以根据需要动态调整。

简单的说就是具有相同特性的数据结构的集合。 

顺序表可以实现手机的通讯录的项目

讲解完顺序表我们会讲解通讯录项目 

在手机通讯录中,通常需要存储联系人的姓名、电话号码、电子邮件等信息,这些信息可以被组织成一个顺序表。每个联系人信息可以作为一个元素存储在顺序表中,而顺序表的每个元素可以包含多个字段,例如姓名、电话号码等。 

———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 

顺序表的实现逻辑

顺序表实现涉及的点

1. 数据存储结构:

顺序表通常使用数组来实现,因为数组提供了随机访问元素的能力,且在数组的基础上可以很容易地实现插入和删除操作。数组的大小在静态顺序表中是固定的,而在动态顺序表中可以根据需要动态调整。
2. 索引机制:

顺序表通过索引来访问元素,索引通常是一个整数,表示元素在数组中的位置。由于数组索引是从0开始的,因此数组中的第一个元素可以通过索引0访问,最后一个元素可以通过索引n-1访问,其中n是数组的长度。
3. 扩容和缩容:

动态顺序表需要在数组满时进行扩容,即增加数组的容量以容纳更多元素。扩容通常涉及到创建一个新的更大的数组,并将现有元素复制到新数组中。当数组中实际存储的元素数量远小于容量时,可以进行缩容以节省内存。
4. 插入操作:

向顺序表插入元素时,通常在数组的末尾进行操作。如果数组未满,直接在末尾添加新元素;如果数组已满,则需要先扩容,然后再插入元素。
5. 删除操作:

从顺序表删除元素时,通常也是在数组的末尾进行操作。删除操作后,需要将后续的元素向前移动一个位置以填补空缺。如果删除的是最后一个元素,则不需要移动。如果数组变得较小,可以考虑进行缩容。
6. 访问操作:

顺序表支持通过索引直接访问元素,这使得访问时间复杂度为O(1)。也可以通过索引来修改表中的元素。
7. 迭代:

顺序表可以很容易地进行迭代,即按照元素的顺序逐个访问每个元素。迭代通常使用循环结构实现。
8. 内存管理:

动态顺序表需要管理内存分配和释放,以避免内存泄漏和溢出。在C语言等需要手动管理内存的编程语言中,这通常涉及到使用`malloc`、`realloc`和`free`等函数。
9. 数据类型:

顺序表可以存储任何类型的数据,包括数字、字符串、对象等。
10. 容量和大小:

顺序表在创建时分配一个初始容量,用于存储元素。容量是指数组可以容纳的最大元素数量。大小是指当前存储在顺序表中的元素数量。

顺序表实现的白话解释

1.首先我们需要创建三个文件也就是头文件,实现文件,以及测试文件

2.我们需要在顺序表的头文件里面创建一个结构体,进行声明,并且创建增删减改的函数。

3.但是在此之前我们使用的逻辑是动态开辟的空间的顺序表的逻辑,所以我们需要的是开辟空间,那么开辟空间还需要销毁空间,使用开辟的空间之前我们需要初始化空间。

4.初始化空间之后,我们可以对顺序表的内容进行增删减改,那么增删减改里面又涉及到(头部删除)(尾部删除)(头部插入)(尾部插入)(指定位置插入)(指定位置删除)

5.最后我们可以打印出来,或者监视,观察是否插入成功。

6.所以我们产生的逻辑可以是

所以我们顺序表的基本逻辑已经完成

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

顺序表的实现

这里我们是按照上述的实现的白话解释为逻辑进行实现的。

下面让我来进行讲解

1.创建三个文件(顺序表)

2.在头文件里面创建结构体(顺序表)

结构体初阶 知识点的补充(初始化,typedef,访问,声明,传参,类型,调用)-CSDN博客

这里为了方便下面的书写,我们把顺序表名字重命名为SL  

这里可以看见,我们定义类型的时候,不是直接用int,而是定义了一个类型,就像宏定义一样,这里的目的是可替代性强,

拿这个举例:通讯录的实现逻辑是顺序表,但是结构类型不是int类型,而是结构体的类型,那么此时如果我们一个一个进行修改的话,很麻烦。但是要是直接定义类型的话,我们只需要更改一行代码,就可以完成。

typedef int SLDataType;//这里定义了一个类型,目的是方便进行定义的时候代码的替代性强

typedef struct Seqlist
{
	SLDataType* arr;//首元素的地址,因为这里我们采取的动态开辟
	int size;//有效数据个数
	int capacity;//空间大小
}SL;

这里提醒一下,.c文件不能忘记包含头文件

3.罗列需要的函数(顺序表)

//开辟空间
void SLCheckCapacity(SL* ps);

//顺序表初始化
void SLInit(SL* ps);

//顺序表的销毁
void SLDestroy(SL* ps);

//打印
void SLprintf(SL ps);

//头部插入删除/尾部插入删除
void SLPushBack(SL* ps, SLDataType x);//尾部的插入
void SLPushFront(SL* ps, SLDataType x);//头部的插入

void SLPopBack(SL* ps);//尾部的删除
void SLPopFront(SL* ps);//头部的删除

//指定位置之前插入/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);//指定位置之前插入数据
void SLErase(SL* ps, int pos);//删除指定位置的数据

// 指定位置的查找,因为需要返回所在的位置,所以需要用int的类型
int SLFind(SL* ps, SLDataType x);

4.函数的实现(顺序表初始化)

这里是顺序表的初始化。

解释一下,首先 顺序表的初始化也就是初始化我们创建的结构体,在我们创建的结构体里面我们可以看到

第一个是int类型的指针类型,指向的开辟空间的首元素的地址,我们此时还没有开辟空间,所以我们初始化的时候先指向空指针

int size;//有效数据个数,什么意思呢,也就是我们实际的使用空间的大小,当我们使用的时候才会占据

int capacity;//空间大小,我们开辟空间的大小,往往开辟的空间是按照倍数进行开辟的,所以就算是按照二倍进行创建,那么原来的空间的大小是4,扩容之后变成8,但是此时实际使用的可能还是4,所以这次是需要空间大小,和实际空间大小的目的

#include"seqlist.h"
//初始化
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
	//指针指向null,空间和大小初初始化的时候都等于0
}

5.函数的实现(顺序表销毁)

这里我们先写出空间的销毁函数,因为简单,好理解。我们知道空间创建,使用完之后就需要进行销毁。

这个逻辑是什么呢

首先就是只要指向的空间是arr,并且此时不是空,也就是存在空间的情况下,我们才进行销毁,要是你不存在空间,直接进行销毁,又可能报错。 

最后也就是让两个空间大小和实际大小都为0.那么为了书写方便,我们直接简化了

//顺序表的销毁
void SLDestroy(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);//释放指向的空间,所以我们这里二次了解到,arr才是空间,剩余两个一个是实际占用空间,一个是空间大小
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;

//等价于
//	ps->size =0;
//  ps->capacity = 0;
}

5.函数的实现(顺序表空间的申请)

空间的申请,什么时候进行申请,申请用什么函数,我们逐步解答

动态内存函数的使用和综合实践-malloc,free,realloc,calloc-CSDN博客

首先就是申请用什么函数

用realloc函数,一张图直接明确的表述为什么是realloc函数,因为realloc函数是连续的,而malloc函数不是连续的,是链表里面使用的。

其次就是什么时候需要申请空间

那当然是实际空间不够用的时候,才需要申请空间,

空间申请的逻辑

在数学逻辑上,申请空间的时候我们往往是采取二倍或者四倍的方式进行空间的申请(偶数倍),这样不会导致空间不够使用

逻辑讲解 

这里我们首先是进行判断,是不是需要申请空间,如果是需要申请的话,我们再申请,不需要的话就直接跳出。 

realloc函数的特点是开辟新的空间赋值到新空间,并且销毁旧的空间。需要知道一点的是,只要是新空间的开辟就可能失败,所以我们防止失败,我们先进行新的参数接受开辟的空间,没有问题的情况下我们再进行赋值接受。

 三木操作符的目的是进行判断因为你申请的空间可能是0,那么如果是0的情况下,你再怎么乘也是0的空间大小,所以我们进行一个三目操作符的判断,当初始空间的大小是0的情况下,我们给四个整形的空间大小,当不为0的时候,我们把空间大小乘以二倍,赋值给变量。

然后realloc函数里面,这里扩容的大小是字节大小,所以需要再*类型的字节,这里你可以直接*4,但是这样的话代码的可替代性就弱了,我们直接是计算int的字节大小也就是

sizeof(int) 

//开辟空间
void SLCheckCapacity(SL* ps)
{
	//插入空间之前看看,空间是否够不够
	if (ps->capacity == ps->size)//当空间大小等于size的时候,size也就是实际占用的大小
	{
		//申请空间
		//realloc动态申请空间,void* realloc(void* ptr, size_t size);
		//申请的空间的大小一般是双倍进行申请空间
		//这里采取三目操作符,目的是判断当前的空间的大小是否为0,
		//如果是0的情况下,则此时说明哪怕开辟空间你*0了最后也是0,所以给定一个基础的4个整形的字节大小,也就是4
		//如果不等于0的情况下,可以继续进行双倍的空间的扩展
		//realloc申请空间的时候可能申请失败,所以我们利用一个变量进行接收

		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//接收空间大小的变量
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//申请多大的字节的空间
		if (tmp == NULL)
		{
			perror("realloc:");
			exit(1);
		}
		//空间申请成功
		ps->arr = tmp;//空间申请成功,然后赋值给结构体里面,空间申请成功,空间大小改变,为0变成4个类型,不为0变成双倍的类型
		ps->capacity = newCapacity;//同时我们改变实际的空间的大小,2 * ps->capacity;而我们开辟的时候开辟的是字节的大小,申请成功,空间大小改变,首元素的地址改变
	}
}

 6.函数的实现(顺序表的打印)

这里我们先写打印的函数,目的是方便观察

逻辑很简单你把这个顺序表当成 数组的打印你会发现一下就看懂了

起始条件是0,也就是数组的0下标,结束条件是实际的大小,因为只有实际的大小才有内容、注意观察打印这里我们没有采取指针的方式,所以.就可以,如果是指针 那就需要箭头了

//打印
void SLprintf(SL ps)
{
	for (int i = 0; i < ps.size; i++)
	{
		printf("%d ", *(ps.arr + i));//这里需要进行解应用,因为这里传递的是首元素地址,解应用之后才是元素
	}
	printf("\n");
}

 7.函数的实现(顺序表的头部的插入)

首先我们看看头插是插入到哪里,显而易见,我们是在开头插入一个元素

所以我们需要什么

1.先判断空间够不够(当实际空间等于使用空间的时候,自然会扩展,当实际空间是4的时候,使用是3的时候,刚好还可以插入一个空间,而开始创建的空间我们就给了四个整形类型的空间)

2.整体往后移动一位(也就是下标是0的往后走,变成下标是1的数值,以此类推)

3,把数值插入到头部(那么此时也就是让ps->arr[0]=x;)也就是把需要插入的数值插入到顺序里面

4.实际大小需要++,(最后我们的原来空间是4,占据3,那么此时我们++,变成实占据的空间也变成4,那么下一次进行开辟空间的时候只要调用申请空间,自然会扩容)

5.进行断言,不能传递空指针

//头部插入
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);//开辟空间函数
	//这里的目的是让顺序表的整体的空间往后移动一位
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;//arr[0]插入首元素的地址,因为已经把后面的数值向后移动了
	ps->size++;//这里因为已经插入,所以size实际占用的空间向后移动,在之后的开辟的时候,如果等于,然后再继续开辟
}

 8.函数的实现(顺序表的尾部的插入)

尾部的插入的逻辑我们来看看,也就顾名思义,最后一个空间插入数值

所以我们需要什么

那么此时就很简单了

1.判断空间够不够

2.够的话让ps->arr[ps->size]=x;把最后一个空间输入数值,然后再把使用的空间++就可以

(这里说明一下,这里是arr的size的下标,也就是如果是0下标需要插入尾插,那么size=1,也就是在第二个元素进行插入,这里不要搞混)

3.不够的话开辟空间,然后循环操作

 SLDataType x(需要插入的数值)

//尾部插入
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);//这里进行一个断言,防止传的时候是一个空指针
	SLCheckCapacity(ps);//开辟空间
	ps->arr[ps->size] = x;
	ps->size++;//这里是实际的大小,也是就在空间大小和实际占据内存多少进行对比的时候需要的

	//ps->arr[ps->size++] = x;//也可以写成这个模式

}

 9.函数的实现(顺序表的尾部的删除)

尾部的删除是很简单的

所以我们需要什么

1.size--(我们只需要舍弃尾部的数据就可以了,也就是把实际空间减少一个 ,就尾部删除了一个,所以也就是我们连赋值都可以舍弃,赋值是为了防止越界的时候,搞混淆,你不小心越界了,这个数值的空间地址还没使用,我们很尴尬,可能会)

2.结束,并且实际使用空间大小--

//尾部的删除
void SLPopBack(SL* ps)
{
	assert(ps);//首先是传参的指针不为空
	assert(ps->size);//其次是删除的时候,里面是有实际的内容的,不是空的,也就是顺序表不为空
	//ps->arr[ps->size - 1]=-1;//这里是赋值了,其实也可以不赋值,因为是直接舍弃这个空间
	ps->size--;
}

 10.函数的实现(顺序表的头部的删除)

头部的删除可能会稍微比尾部的删除复杂一点,但是也只是那么一点点

所以我们需要什么

1.我们只需要从前往后进行覆盖就可以

2.实际使用的空间--

//头部的删除
void SLPopFront(SL* ps)
{
	assert(ps);//首先是传参的指针不为空
	assert(ps->size);//其次是删除的时候,里面是有实际的内容的,不是空的,也就是顺序表不为空
	//这里的逻辑就是整体往后移动一位,也就是让后一个数值直接覆盖前一个数值,最后让实际空间大小--一个就可以
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

12.函数的实现(顺序表的指定位置插入)

这里有一点难度因为我们需要找到指定的位置,我们插入数值,所以我们的函数的参数也就是三个


//指定前位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);//这里插入的时候,插入的位置需要在实际内存里面,pos指定的是指定位置
	SLCheckCapacity(ps);//判断空间够不够
	//这里不能从前往后进行移动,因为会导致数值的覆盖,只能从后往前进行覆盖
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

我们需要什么

1.判断空间够不够

2.找到需要插入的下标的位置

3.循环移动之后的数值,这里需要注意这里数值的循环覆盖只能是从size开始,--到pos也就是我们需要的所在位置的下标,因为会导致覆盖,也就是1 2 3 4,从下标为1的数值前进行插入,所以如果是从前往后进行覆盖,会导致,1 【插入数值】 2 2 2.

4.最后size++,(实际占据的空间大小++)

测试一下没有问题

13.函数的实现(顺序表的指定位置删除)

指定位置的删除的关键点是找到位置,然后进行覆盖,很简单

需要注意最后不要忘记实际空间剩余--,进行计数 

//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//依旧是有效区间
	for (int i = pos; i < ps->size - 1; i++)//这里必须进行-1,因为最后是ps->arr[size]=ps->arr[size+1];会导致越界访问
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

14.函数的实现(顺序表的指定位置的查找)

这里就非常简单了,只需要循环遍历就可以了

//指定位置的查找
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size); 
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] = x)
		{
			//找到了
			return i;
		}
	}
	//没有找到
	return -1;
}

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

顺序表的代码

test.c

//顺序表的测试
#include"seqlist.h"
void SLTest01()
{
	SL s1;
	//初始化
	SLInit(&s1);
	//测试尾插
	SLPushBack(&s1, 1);
	SLprintf(s1);
	SLPushBack(&s1, 2);
	SLprintf(s1);
	SLPushBack(&s1, 3);
	SLprintf(s1);
	SLPushBack(&s1, 4);
	SLprintf(s1);
	//测试头插
	SLPushFront(&s1, 9);
	SLprintf(s1);
	SLPushFront(&s1, 8);
	SLprintf(s1);
	SLPushFront(&s1, 7);
	SLprintf(s1);
	SLPushFront(&s1, 6);
	SLprintf(s1);
	//尾部的删除测试
	SLPopBack(&s1);
	SLprintf(s1);
	SLPopBack(&s1);
	SLprintf(s1);
	SLPopBack(&s1);
	SLprintf(s1);
	SLPopBack(&s1);
	SLprintf(s1);
	//头部的删除测试
	SLPopFront(&s1);
	SLprintf(s1);
	SLPopFront(&s1);
	SLprintf(s1);
	SLPopFront(&s1);
	SLprintf(s1);
	//指定位置的插入
	SLInsert(&s1, 0, 0);
	SLprintf(s1);
	SLInsert(&s1, s1.size, 99);
	SLprintf(s1);
	SLInsert(&s1, s1.size, 88);
	SLprintf(s1);
	SLInsert(&s1, s1.size, 77);
	SLprintf(s1);
	//删除指定位置的数据
	SLErase(&s1, 1);
	SLprintf(s1);
	SLErase(&s1, 0);
	SLprintf(s1);
	//指定位置的查找
	int ret1 = SLFind(&s1, 0);
	if (ret1 != -1)
	{
		printf("找到了所在位置是:%d\n", ret1);
	}
	else
	{
		printf("没找到。\n");

	}
	int ret2 = SLFind(&s1, 99);
	if (ret2 != -1)
	{
		printf("找到了所在位置是:%d\n", ret2);
	}
	else
	{
		printf("没找到。\n");

	}

}
int main()
{
	SLTest01();
	return 0;
}

seqlist.h

//顺序表的头文件
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#define N 100
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"contact.h"//因为头文件是不能互相包含的,所以我们这里采取顺序表包含通讯录的头文件
//这样我们调用顺序表的时候,自然也就是调用了通讯录的头文件

//但是此时我们还需要进行前置声明,因为头文件是不能相互包含的,但是我们还需要顺序表里面的内容


typedef int SLDataType;//这里定义了一个类型,目的是方便进行定义的时候代码的替代性强
//typedef peoInfo SLDataType;//这里定义了一个类型,目的是方便进行定义的时候代码的替代性强

typedef struct Seqlist
{
	SLDataType* arr;//首元素的地址,因为这里我们采取的动态开辟
	int size;//有效数据个数
	int capacity;//空间大小
}SL;//typedef struct Seqlist SL;//结构体的重命名

//开辟空间
void SLCheckCapacity(SL* ps);

//顺序表初始化
void SLInit(SL* ps);

//顺序表的销毁
void SLDestroy(SL* ps);

//打印
void SLprintf(SL ps);

//头部插入删除/尾部插入删除
void SLPushBack(SL* ps, SLDataType x);//尾部的插入
void SLPushFront(SL* ps, SLDataType x);//头部的插入

void SLPopBack(SL* ps);//尾部的删除
void SLPopFront(SL* ps);//头部的删除

//指定位置之前插入/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);//指定位置之前插入数据
void SLErase(SL* ps, int pos);//删除指定位置的数据

// 指定位置的查找,因为需要返回所在的位置,所以需要用int的类型
int SLFind(SL* ps, SLDataType x);


seqlist.c

//顺序表的实现
#include"seqlist.h"
//初始化
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
	//指针指向null,空间和大小初初始化的时候都等于0
}

//顺序表的销毁
void SLDestroy(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);//释放指向的空间
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

//打印
void SLprintf(SL ps)
{
	for (int i = 0; i < ps.size; i++)
	{
		printf("%d ", *(ps.arr + i));//这里需要进行解应用,因为这里传递的是首元素地址,解应用之后才是元素
	}
	printf("\n");
}

//开辟空间
void SLCheckCapacity(SL* ps)
{
	//插入空间之前看看,空间是否够不够
	if (ps->capacity == ps->size)//当空间大小等于size的时候,size也就是实际占用的大小
	{
		//申请空间
		//realloc动态申请空间,void* realloc(void* ptr, size_t size);
		//申请的空间的大小一般是双倍进行申请空间
		//这里采取三目操作符,目的是判断当前的空间的大小是否为0,
		//如果是0的情况下,则此时说明哪怕开辟空间你*0了最后也是0,所以给定一个基础的4个整形的字节大小,也就是4
		//如果不等于0的情况下,可以继续进行双倍的空间的扩展
		//realloc申请空间的时候可能申请失败,所以我们利用一个变量进行接收

		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//接收空间大小的变量
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//申请多大的字节的空间
		if (tmp == NULL)
		{
			perror("realloc:");
			exit(1);
		}
		//空间申请成功
		ps->arr = tmp;//空间申请成功,然后赋值给结构体里面
		ps->capacity = newCapacity;//同时我们改变实际的空间的大小,2 * ps->capacity;而我们开辟的时候开辟的是字节的大小
	}

}

//尾部插入
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);//这里进行一个断言,防止传的时候是一个空指针
	SLCheckCapacity(ps);//开辟空间
	ps->arr[ps->size] = x;
	ps->size++;//这里是实际的大小,也是就在空间大小和实际占据内存多少进行对比的时候需要的

	//ps->arr[ps->size++] = x;//也可以写成这个模式

}

//头部插入
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);//开辟空间
	//这里的目的是让顺序表的整体的空间往后移动一位
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;//arr[0]插入首元素的地址,因为已经把后面的数值向后移动了
	ps->size++;//这里因为已经插入,所以size实际占用的空间向后移动,在之后的开辟的时候,如果等于,然后再继续开辟
}

//尾部的删除
void SLPopBack(SL* ps)
{
	assert(ps);//首先是传参的指针不为空
	assert(ps->size);//其次是删除的时候,里面是有实际的内容的,不是空的,也就是顺序表不为空
	ps->arr[ps->size - 1] = -1;//这里是赋值了,其实也可以不赋值,因为是直接舍弃这个空间
	ps->size--;
}

//头部的删除
void SLPopFront(SL* ps)
{
	assert(ps);//首先是传参的指针不为空
	assert(ps->size);//其次是删除的时候,里面是有实际的内容的,不是空的,也就是顺序表不为空
	//这里的逻辑就是整体往后移动一位,也就是让后一个数值直接覆盖前一个数值,最后让实际空间大小--一个就可以
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}


//指定前位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);//这里插入的时候,插入的位置需要在实际内存里面,pos指定的是指定位置
	SLCheckCapacity(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++)//这里必须进行-1,因为最后是ps->arr[size]=ps->arr[size+1];会导致越界访问
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

//指定位置的查找
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] = x)
		{
			//找到了
			return i;
		}
	}
	//没有找到
	return -1;
}




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1581472.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web前端-Ajax

Ajax 概念:Asynchronous JavaScript And XML,异步的JavaScript和XML。 作用: 1.数据交换:通过Ajax可以给服务器发送请求,并获取服务器响应的数据。 2.异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用的校验等等…

「44」直播间换脸,揭开神秘的面纱……

「44」换脸神器 让你瞬间秒变「明星脸」带货 DeepFace是Facebook的人脸识别系统之一&#xff0c;旨在在照片和视频中准确识别和标识人脸。它使用深度学习和神经网络技术来进行高度精确的人脸匹配和验证。 DeepFace利用了大量的训练数据和先进的人脸识别算法&#xff0c;能够…

Redis中的集群(二)

节点 集群数据结构 redisClient结构和clusterLink结构的相同和不同之处 redisClient结构和clusterLink结构都有自己的套接字描述符和输入、输出缓冲区&#xff0c;这两个结构的区别在于&#xff0c;redisClient结构中的套接字和缓冲区是用于连接客户端的&#xff0c;而clust…

使用Vivado Design Suite进行功率优化

功率优化是一个可选步骤&#xff0c;它通过使用时钟门控来优化动态功率。它既可以在Project模式下使用&#xff0c;也可以在Non-Project模式下使用&#xff0c;并且可以在逻辑优化之后或布局之后运行&#xff0c;以减少设计中的功率需求。功率优化包括Xilinx的智能时钟门控解决…

git分支-分支工作流

分支工作流 现在已经掌握了分支和合并的基础知识&#xff0c;可以或应该如何使用它们&#xff1f;在本节中&#xff0c;我们将介绍一些常见的工作流程&#xff0c;这种轻量级的分支使得这些工作流程成为可能&#xff0c;因此我们可以决定是否要将它们纳入到自己的开发周期中。…

每天五分钟深度学习:如何理解逻辑回归算法的假设函数?

本文重点 我们在机器学习专栏中已经学习了逻辑回归算法,本次课程我们将重温逻辑回归算法,该算法适用于二分类的问题,本文主要介绍逻辑回归的假设函数。我们在学习线性回归算法的时候,我们已经知道了线性回归算法的假设hθ(x)=θTX(参数θ的转置*X),但是对于逻辑回归而言…

使用wget下载Github代码文件

前言 使用wget无法直接从Github链接直接下载代码文件&#xff0c;下文介绍解决方案。 解决方案 确保要下载的代码文件是公开文件&#xff0c;否则无法通过wget下载。 进入Github并找到要下载的代码文件&#xff0c;例如&#xff1a; 进入文件后&#xff0c;点击文件右侧的…

Windows完全卸载MySQL后再下载安装(附安装包)

目录 友情提醒第一章&#xff1a;如何完全卸载干净mysql教程&#xff08;三个步骤完全卸载&#xff09;1&#xff09;步骤一&#xff1a;卸载程序2&#xff09;步骤二&#xff1a;删除文件3&#xff09;步骤三&#xff1a;删除注册表信息 第二章&#xff1a;下载软件两种方式1&…

R语言数据可视化:ggplot2绘图系统

ggpolt2绘图系统被称为R语言中最高大上的绘图系统&#xff0c;使用ggplot2绘图系统绘图就像是在使用语法创造句子一样&#xff0c;把数据映射到几何客体的美学属性上。因此使用ggplot2绘图系统的核心函数ggplot来绘图必须具备三个条件&#xff0c;数据data&#xff0c;美学属性…

视频插针调研

视频插针 1、评估指标2、准确度3、实时4、视频流处理3、实时RIFE视频插帧测试 1、评估指标 参考&#xff1a;https://blog.csdn.net/weixin_43478836/article/details/104159648 https://blog.csdn.net/weixin_43605641/article/details/118088814 PSNR和SSIM PSNR数值越大表…

【CSS】MDN

一、CSS构建 1.1 CSS选择器 1.1.1 元素选择器 标签不区分大小写 示例&#xff1a; span {background-color: yellow; }1.1.2 通用选择器 选择文档中所有内容&#xff0c;重置样式表中使用较多 鉴于div:first-child&#xff08;代表所有的div元素&#xff0c;并且是第一个…

IT行业网络安全守护者-行云管家云堡垒机

IT行业即信息技术行业&#xff0c;是一个涵盖广泛的行业领域&#xff0c;主要涉及与信息的处理、存储、传输和应用相关的技术。对于IT行业而言&#xff0c;保障数据安全以及网络安全至关重要&#xff0c;一不小心就容易造成数据泄露事件。今天我们小编就给大家介绍一下IT行业网…

Docker 搭建私有镜像仓库

一、镜像仓库简介 Docker的镜像仓库是一个用于存储和管理Docker镜像的中央位置。镜像仓库的主要作用是提供一个集中的地方&#xff0c;让用户可以上传、下载、删除和共享Docker镜像。镜像仓库又可以分为公共镜像仓库和私有仓库镜像仓库&#xff1a; 公共镜像仓库 Docker Hub 是…

ZStack Cloud 5.0.0正式发布——Vhost主存储、隔离PVLAN网络、云平台报警优化、灰度升级增强四大亮点简析

近日&#xff0c;ZStack Cloud 5.0.0正式发布&#xff0c;推出了包含Vhost主存储、隔离PVLAN网络、云平台报警优化、灰度升级增强在内的一系列重要功能。云主机管理、物理机运维、密评合规、灾备服务等诸多使用场景和功能模块均有更新&#xff0c;为您带来更完善的平台服务、更…

OJ 进制转换 判断进制【C】

进制转换 好久没写C&#xff0c;太生疏了&#xff0c;记一下我卡住的几个点 1.C没有字符串这种数据类型&#xff0c;要实现读入一个未知长度的字符串比较简单的方法 建立一个一定比这个字符串长的数组&#xff0c;而字符串在数组中的存储是会自动以\0结尾的&#xff0c;所以遍…

macOS制作C/C++ app

C/C制作macOS .app 一、 .app APP其实是一个文件夹结构&#xff0c;只不过mac的界面中让它看起来像一个单独的文件。 在shell终端或者右键查看包结构即可看到APP的目录结构。 通常的app目录结构如下&#xff1a; _CodeSignature, CodeResources 一般为Mac APP Store上架程序…

三极管结构难?——秒了

前边我们已经学完了PN结&#xff0c;二极管&#xff0c;在分析了二极管后&#xff0c;我们对这些东西有了一定深度的了解&#xff0c;但是只给我们一个二极管去研究&#xff0c;这玩意好像真的没啥大用&#xff0c;其实我们追求的是用半导体材料去代替电子管的放大作用&#xf…

51单片机入门_江协科技_21.2_74HC595 在Proteus中模拟8x8点阵屏环境搭建

1. 为了在proteus中模拟学习江协科技51单片机教程&#xff0c;需要在proteus中搭建74HC595驱动8x8点阵屏的仿真环境&#xff1b; 1.1. 因为连接单片机P0口作为点阵屏负极&#xff08;行选&#xff09;&#xff0c;所以需要先在P0口上接上上拉电阻RESPACK 8&#xff0c;1k欧姆阻…

K8S哲学 - kubectl

Kubectl is the Kubernetes cli version of a swiss army knife, and can do many things. Kubernetes coordinates a highly available cluster of computers that are connected to work as a single unit k8s production-ready. 概念 kubectl 和 Kubernetes API 区别

【前端】学习路线

1、基础 1.1 HTML 菜鸟教程-主页&#xff1a;https://www.runoob.com/ 可以学习&#xff1a;HTML、CSS、Bootstrap等 1.2 CSS 《通用 CSS 笔记、建议与指导》 1.3 JavaScript 1&#xff09;入门&#xff1a;JavaScript 的基本语法 2&#xff09;进阶&#xff1a;现代 …