【数据结构实战项目】C语言实现数据结构顺序表万字详解(附完整运行代码)

news2024/9/21 14:45:55

🦄个人主页:修修修也

🎏所属专栏:数据结构

⚙️操作环境:Visual Studio 2022


一.了解项目功能

在本次项目中我们的目标是实现一个顺序表:

顺序表使用动态内存分配,可以用来存储任意数量的同类型数据.

顺序表需要包含三个要素:存储数据的数组arr,顺序表的当前存储容量capacity,线性表当前的长度size.

顺序表提供的功能有:

  1. 顺序表的初始化
  2. 顺序表元素的查满扩容.
  3. 顺序表元素的尾插.
  4. 顺序表元素的头插.
  5. 顺序表元素的任意指定位置插入.
  6. 顺序表的尾删.
  7. 顺序表的头删.
  8. 顺序表元素的任意指定位置删除.
  9. 顺序表的查找.
  10. 顺序表的打印.
  11. 顺序表的销毁.

二.项目功能演示

要编写一个顺序表项目,首先要明确我们想要达到的效果是什么样,下面我将用vs2022编译器来为大家演示一下顺序表运行时的样子:

顺序表的C语言实现xi


三.逐步实现项目功能模块及其逻辑详解

通过第二部分对项目功能的介绍,我们已经对顺序表的功能有了大致的了解,虽然看似需要实现的功能很多,貌似一时间不知该如何下手,但我们可以分步分模块来分析这个项目的流程,最后再将各各部分进行整合,所以大家不用担心,跟着我一步一步分析吧!


!!!注意,该部分的代码只是为了详细介绍某一部分的项目实现逻辑,故可能会删减一些与该部分不相关的代码以便大家理解,需要查看或拷贝完整详细代码的朋友可以移步本文第四部分。


1.实现顺序表程序菜单

菜单部分的逻辑比较简单,就是利用C语言printf函数打印出这个菜单界面即可。基础问题就不过多赘述了,代码如下:

//菜单函数
void SeqMenu(int size)      
{
	printf("******************************\n");
	printf("******请选择要进行的操作******\n");
	printf("******1.顺序表的定点插入******\n");
	printf("******2.顺序表的定点删除******\n");
	printf("******3.顺序表的定位查找******\n");
	printf("******4.顺序表的数据打印******\n");
	printf("******5.顺序表的销毁    ******\n");
	printf("******0.退出程序        ******\n");
	printf("** tips:数据元素位置从0开始 **\n");
	printf("**   顺序表的当前长度:%d     **\n",size);//每次打印菜单都向用户显示顺序表当前长度
	printf("******************************\n");
	printf("请选择:>");
}

2.实现顺序表程序功能可循环使用

由于我们要实现顺序表的功能可以反复使用的逻辑,因此选择do...while的循环语句来实现这一部分的逻辑,该部分每步的详细解释见代码注释:

int main()
{
    SL s; // 创建顺序表变量s
    SLInit(&s); // 调用初始化函数初始化顺序表

    int swi = 0; // 定义变量swi作为do...while循环的终止条件,以及switch语句的运行条件
    do // 使用do...while实现顺序表功能可以反复使用
    {
        SeqMenu(s.size); // 打印菜单,打印菜单提示用户选择
        scanf("%d", &swi); // 存储用户的输入选项

        switch (swi) // 根据选项执行相应操作
        {
        case 0://当用户选择'0',退出程序
            printf("您已退出程序:>\n");
            // 释放链表内存
            SLDestroy(&s);
            break;

        case 1://当用户选择'1',插入元素
            printf("请输入要插入的数据:>");
            int insert_data = 0;
            scanf("%d", &insert_data);
            printf("请输入要插入的数据位置:>");
            int insert_pos = 0;
            scanf("%d", &insert_pos);
            SLInsert(&s, insert_pos, insert_data); // 在顺序表中插入数据
            printf("已成功插入:>\n");
            break;

        case 2://当用户选择'2',删除元素
            printf("请输入要删除的数据位置:>");
            int delete_pos = 0;
            scanf("%d", &delete_pos);
            SLErase(&s, delete_pos); // 在顺序表中删除数据
            printf("删除成功:>\n");
            break;

        case 3://当用户选择'3',查找元素
            printf("请输入要查找的数据的值:>");
            int find_data = 0;
            scanf("%d", &find_data);
            int find_pos = SLFind(&s, find_data); // 在顺序表中查找数据
            if (find_pos != -1)
            {
                printf("找到了,数据元素%d的下标为%d\n", find_data, find_pos);
            }
            else
            {
                printf("没找到,顺序表中可能不存在此数据:<\n");
            }
            break;

        case 4://当用户选择'4',打印元素
            printf("打印数据:>\n");
            SLPrint(&s); // 打印顺序表中的数据
            break;

        case 5://当用户选择'5',销毁顺序表
            printf("确定要销毁顺序表吗?:>\n");
            printf("销毁顺序表输入:1\n");
            printf("取消销毁顺序表输入:0\n");
            int destroy = 0;
            printf("请输入:>");
            scanf("%d", &destroy);
            if (destroy)
            {
                SLDestroy(&s); // 销毁顺序表
                SLInit(&s); // 重新初始化顺序表
                printf("已成功销毁:>\n");
            }
            else
            {
                printf("已取消销毁:>\n");
            }
            break;

        default://当用户输入了非选项数字时,提醒用户重新输入
            printf("输入错误,请重新输入\n");
            break;
        }
    } while (swi); // 当swi为0时退出循环,结束程序

    return 0;
}

3.创建顺序表

创建顺序表成员的结构体应该包括:存储数据的数组arr,顺序表的当前存储容量capacity,线性表当前的长度size.

因此我们创建SeqList结构体类型时应由一个数组及两个整型组成.

这里的第一行使用的typedef类定义的作用是方便我们后续在使用顺序表时对存储的数据类型做更改,比如后续我们不想存储int类型数据了,就可以很方便的在这里对数组类型做更改.比如改成char类型,或者double类型,甚至改成任意自己构造的结构类型.在之前的实战项目通讯录中,我们就创建过类似的自定义结构体:如下图.

ps:想了解通讯录程序的朋友可以移步这篇博客: 

【C语言实战项目】通讯录icon-default.png?t=N7T8https://blog.csdn.net/weixin_72357342/article/details/132265734?spm=1001.2014.3001.5502

第二行的宏定义可以方便我们后续对顺序表的初始大小做调整.

有还不太了解宏定义#define及其使用方法的朋友可以移步这里:

【C语言】什么是宏定义?(#define详解)icon-default.png?t=N7T8https://blog.csdn.net/weixin_72357342/article/details/133607987?spm=1001.2014.3001.5502

综上,该部分代码如下:

typedef int SLDataType;//将结构体数组重命名,方便后面修改线性表的成员
#define INIT_CAPACITY 4

//动态顺序表——按需申请
typedef struct SeqList     //对结构体重命名为SL
{
	SLDataType *arr;   //一个指针指向一片连续的空间
	int size;          //有效数据个数
	int capacity;      //空间容量
}SL;

4.初始化顺序表

初始化顺序表的逻辑不难,但代码编写的细节上可能会需要多注意一些.

首先在进入初始化程序后,我们应当对函数传进来的参数做一个检验,即检验ps指针是否为空指针,如果该指针为空的话,那么指针变量就没有指向任何有效的内存地址,即指针变量的值为0或NULL。这时我们再进入下一步强行开辟内存空间就很可能会导致程序出现一些问题:

tips:用空指针接收malloc函数返回值的危害是非常严重的,因为它会导致程序出现未定义的行为,甚至可能会导致程序崩溃

当我们调用malloc函数时,它会在堆上分配一块指定大小的内存,并返回指向该内存的指针。如果我们用空指针来接收malloc函数返回的指针,那么就相当于没有为分配的内存分配任何指针变量,这意味着我们无法访问该内存块,也无法释放该内存块,因为我们没有指向它的指针。

这种情况下,如果我们试图访问该内存块,就会发生未定义的行为,也可能会导致程序崩溃。此外,如果我们忘记释放该内存块,就会导致内存泄漏,这会导致程序消耗大量的内存资源,最终导致程序崩溃或者系统变慢

因此,我们应该始终使用有效的指针变量来接收malloc函数返回的指针,以确保我们能够正确地访问和释放动态分配的内存块。

因此,我们可以使用assert来对函数传进来的参数ps进行检验,如果ps为空,那么立刻终止程序,并抛出异常警告程序员.

对assert宏的使用想要更详细了解的朋友可以移步到这:【C语言】库宏assert简介及使用方法详解icon-default.png?t=N7T8https://blog.csdn.net/weixin_72357342/article/details/133822893?spm=1001.2014.3001.5502

需要注意的是,这里我们对传入的ps指针的断言需要与后面我们要实现的链表中的断言作一下区分:顺序表中要求ps不能为空,是因为一旦ps为空,那么传入的指针一定是一个非法的空指针,因为ps为空,不仅代表arr为NULL,还代表size,capacity也都为NULL.这意味着整个顺序表都是不存在的,而不是仅仅只意味着顺序表中没有元素. 但链表中如果传入的头节点指针指向了NULL,并不能说明链表不存在,而只能说明链表中没有元素而已.这点上的不同是它们两者的结构不同导致的.

检验没有问题后,我们就可以开始进行动态内存开辟的相关操作了:

首先我们使用malloc动态开辟一定字节的空间交给ps->arr来使用:

ps->arr =(SLDataType*)malloc(sizeof(SLDataType)*INIT_CAPACITY);

这段代码看似好像很复杂,我给大家将每一小块都拆解一下:

如果对malloc()函数掌握的还是不太好的朋友可以先移步这里:

里面有非常详细的简介及使用方法,在后续查满扩容函数乃至以后的链表,树,图的实现中我们还会经常使用到这个函数:

【C语言】malloc()函数详解(动态内存开辟函数)icon-default.png?t=N7T8https://blog.csdn.net/weixin_72357342/article/details/133971625?spm=1001.2014.3001.5502

该部分的功能实现代码如下:

//顺序表的初始化
void SLInit(SL*ps)
{
	assert(ps);            //查空
	ps->arr =(SLDataType*)malloc(sizeof(SLDataType)*INIT_CAPACITY);   //扩容
	if (ps->arr == NULL)
	{
		perror("malloc fail");       //如果开辟失败,打印错误信息
		return;
	}

	ps->size = 0;                   //将顺序表初始元素设为0
	ps->capacity = INIT_CAPACITY;   //将顺序表初始容量设为INIT_CAPACITY
}

5.顺序表的查满及扩容

在顺序表的查满扩容函数中,其实我们只需要进行简单的四步操作:

1.操作之前先使用assert检查一下ps是否为空指针.

2.判断size与capacity的关系,当顺序表的元素个数一旦等于顺序表的容量时,就使用realloc()函数进行扩容.(一般来说,我们每次扩容的容量是扩容前的2倍时比较合理.)

3.使用realloc()函数和malloc()函数一样,当遇到未开辟成功的情况时需要抛出错误信息.

4.最后记得扩容后要给capacity的值也乘2,和空间真实容量保持一致.

该部分功能实现代码如下:

//顺序表的查满扩容
void SLCheckCapacity(SL* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * ps->capacity * 2);//扩容二倍比较合理
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->arr = tmp;//原地扩容
		ps->capacity *= 2;
	}
}

6.顺序表元素的插入(插前检查容量)

顺序表元素的插入有三种方式:分别是尾插,头插任意指定位置插入.

这三种方式都可以实现顺序表的元素插入,接下来我们分别来看一下这三种插入:

🎏顺序表元素尾插

尾插元素:将元素插入到顺序表的最后一个位置,只需要在arr数组的末尾添加元素即可。该算法的时间复杂度为O(1)

尾插的逻辑非常简单,不需要挪动元素,只需要在插入元素前检查一下顺序表容量是否满了就行.(调用SLCheckCapacity()函数,查满和扩容一起进行)

该部分功能实现代码如下:

//顺序表的插入(尾插)
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);          //检查ps指针不为NULL
	
	SLCheckCapacity(ps);     //首先要检查容量,如果不够要增容
	
	ps->arr[ps->size] = x;    //最后将数据赋值给arr数组中的最后一个元素即可
    ps->size++;
}

🎏顺序表元素头插

头插元素:将元素插入到顺序表的第一个位置,需要将原有的所有元素都向后移动一位。该算法的时间复杂度为O(n),其中n是顺序表中元素的个数

头插的逻辑比尾插复杂一些, 我们需要先将顺序表中的所有元素都向后挪动一位,然后才能在顺序表的首位插入元素.当然,在挪动和插入操作前,我们还是照例要先检查一下顺序表当前容量是否满了.

(同样调用SLCheckCapacity()函数,查满和扩容一起进行)

该部分实现代码如下:

/顺序表的插入(头插)
//头插n个数据时间复杂度是O(n),少用!
void SLPushFront(SL* ps, SLDataType x)
{
	
	assert(ps);
	//插之前检查是不是满了
	SLCheckCapacity(ps);


	//先挪
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}

    //后放
	ps->arr[0] = x;
	ps->size++;
}

🎏顺序表元素插入任意指定位置

任意指定位置插入元素:将元素插入到顺序表中的任意位置,需要将插入位置后的所有元素都向后移动一位。该算法的时间复杂度为O(n),其中n是顺序表中元素的个数

 

任意指定位置插入的逻辑和头插差不多,只不过头插是把所有位置的元素都向后挪动,使头空出来,好插入元素.而指定位置插入是将指定位置后的所有元素向后挪动一位,使指定位置空出来,好在指定位置插入元素.

因此,我们需要先将顺序表中指定位置后的所有元素都向后挪动一位,然后才能在顺序表的指定位置插入元素.当然,在挪动和插入操作前,我们还是要先检查一下顺序表当前容量是否满了以及检查待插入位置pos是否合法(pos<0或pos>ps->size都是越界访问).

(同样调用SLCheckCapacity()函数,查满和扩容一起进行).

该部分实现代码如下:

//顺序表的定点插入
//有定点删除之后就可以不要头插尾插了,直接传参0或者size就行
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	//定点插入,插入点必须在0~size之间(判断).
	assert(pos >= 0 && pos <= ps->size);
    //pos<0或pos>ps->size都是越界访问
	//pos==0,相当于头插,pos==ps->size,相当于尾插

	SLCheckCapacity(ps);

	int end = ps->size - 1;//从顺序表的末尾逐一向后挪动元素
	while (end >= pos)     //当将原本pos位置的元素挪走后,就可以插入新元素了
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	//插入新元素
	ps->arr[pos] = x;
	//元素个数增加
	ps->size++;
}

有了任意指定位置插入函数后我们容易发现,当我们要求在pos=0的位置插入元素时,其实就相当于顺序表的头插了,当我们要求在pos=size的位置插入元素时,其实就相当于顺序表的尾插了.因此,如果写了任意指定位置插入函数,我们就完全不再需要再写头插和尾插函数了.因为任意指定位置插入函数就可以很好的实现头插和尾插的功能.


7.顺序表元素的删除(删前检查是否为空表)

顺序表元素的删除同样有三种方式:分别是尾删,头删任意指定位置删除.

这三种方式都可以实现顺序表的元素删除,接下来我们分别来看一下这三种删除:

🎏顺序表元素尾删

尾删元素:将顺序表的最后一个位置的元素删除,只需要将顺序表的元素个数size-1即可。可以不需要将该位置的数据置为0,因为给size-1实际上是拿走了这块位置的访问权限,没有访问权限时该位置的数据是什么都没有意义.该算法的时间复杂度为O(1)

尾删的逻辑同样很简单,不需要挪动元素,只需要在删除前检查顺序表是否为空表就行,然后将size--一下.(如果为空,则不需要删除,直接返回即可).

该部分功能实现代码如下:

//顺序表的删除(尾删)
void SLPopBack(SL* ps)
{
	//先判断,如果size已经为0了,就不要再删了
	if (ps->size == 0)
		return;

	ps->size--;
	//这里没必要把数据置为0,因为size减少后原位置的数据就无法访问了
	//其次是这里没法把删除的空间free掉,动态内存申请的空间都是"团购"的,要申请一起申请,要释放只能从头一起释放.
}

🎏顺序表元素头删

头删元素:将顺序表的第一个位置的元素删除掉,需要将除了首元素之外的所有元素都向前移动一位。该算法的时间复杂度为O(n),其中n是顺序表中元素的个数

头删的逻辑比尾删复杂一些, 我们需要将顺序表中第一个元素后的所有元素都向前挪动一位,这样刚好原来第一位元素的数据就会被覆盖,即第一个元素被"删除"了.当然,在挪动前,我们还是照例要先检查一下顺序表当前是不是空表.

该部分功能实现代码如下:

//顺序表的删除(头删)时间复杂度O(n^)
void SLPopFront(SL* ps)
{
	assert(ps);
	//判断是否为空,不为空才能删,为空直接报错
	assert(ps->size > 0);

	int begin = 1;      //元素下标为1,即从第二个元素开始挪动
	while (begin < ps->size)  //直到将最后一个元素挪动到size-2的位置上
	{
		ps->arr[begin-1] = ps->arr[begin];
		begin++;
	}
	ps->size--;
}

🎏顺序表删除任意指定位置元素

任意指定位置删除元素:将顺序表的任意位置的元素删除,需要将删除位置后的所有元素都向前移动一位。该算法的时间复杂度为O(n),其中n是顺序表中元素的个数

 

任意指定位置删除的逻辑和头删差不多,只不过头删是把所有第一个元素后的元素都向前挪动,使头位置元素被覆盖.而指定位置删除是将指定位置后的所有元素向前挪动一位,使指定位置被覆盖,以达到删除该元素的效果.

因此,我们需要将顺序表中指定位置后的所有元素都向前挪动一位,以此来达到删除某个元素的效果.当然,在挪动删除操作前,我们还是照例要先检查一下顺序表当前是不是空表,以及检查待删除位置pos是否合法(pos<0或pos>=ps->size都是越界访问).

该部分实现代码如下:

//顺序表的定点删除
//头删和尾删也不用存在了
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//可以间接检查size是否为0.因为pos没法小于0.
    //pos<0或pos>=ps->size都是越界访问

	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->arr[begin - 1] = ps->arr[begin];
		begin++;
	}
	ps->size--;
}

有了任意指定位置删除函数后我们发现,当我们要求在pos=0的位置删除元素时,其实就相当于顺序表的头删了,当我们要求在pos=size-1的位置删除元素时,其实就相当于顺序表的尾删了.因此,如果写了任意指定位置删除函数,我们就完全不再需要再写头删和尾删函数了.


8.顺序表元素的查找

顺序表元素的查找有些类似于数组的元素查找,都是在合法数组范围内遍历整个顺序表的元素即可.

如果在遍历的过程中找到了要查找的元素,就返回该元素的下标,如果遍历完还没找到该元素,则意味着该元素不在顺序表中,因此返回-1.

该部分功能实现代码如下:

//顺序表的查找
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);      //使用assert断言,防止ps为NULL
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;        //找到了,返回元素下标
		}
	}
	return -1;        //没找到,返回-1
}

9.顺序表元素的打印

顺序表的打印逻辑和查找一样简单,使用循环遍历打印元素即可.

该部分功能实现代码如下:

//顺序表的打印
void SLPrint(SL* ps)
{
	assert(ps);      //断言防止pa为NULL
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
	printf("打印成功:>\n");
}

10.顺序表的销毁

当我们使用完顺序表想要退出程序时,就应该将之前动态开辟的内存释放掉,还给操作系统.即销毁顺序表操作.

我们使用free()函数释放掉之前动态开辟的数组arr,然后将arr置为空指针,最后将size,capacity的值置为0即可.

该部分功能实现代码如下:

void SLDestroy(SL* ps)
{
	assert(ps);
	free(ps->arr);//因为arr是malloc出来的,所以free就要释放arr
	ps->arr = NULL;//将arr置为空指针
	ps->capacity = 0;
	ps->size = 0;
}

四.项目完整代码

我们将程序运行的代码分别在三个工程文件中编辑,完整代码如下:

SeqList.c文件

#include"SeqList.h"

//菜单
void SeqMenu(int size)
{
	printf("******************************\n");
	printf("******请选择要进行的操作******\n");
	printf("******1.顺序表的定点插入******\n");
	printf("******2.顺序表的定点删除******\n");
	printf("******3.顺序表的定位查找******\n");
	printf("******4.顺序表的数据打印******\n");
	printf("******5.顺序表的销毁    ******\n");
	printf("******0.退出程序        ******\n");
	printf("** tips:数据元素位置从0开始 **\n");
	printf("**   顺序表的当前长度:%d     **\n",size);
	printf("******************************\n");
	printf("请选择:>");
}

//顺序表的初始化
void SLInit(SL*ps)
{
	assert(ps);
	ps->arr =(SLDataType*)malloc(sizeof(SLDataType)*INIT_CAPACITY);
	if (ps->arr == NULL)
	{
		perror("malloc fail");
		return;
	}
	ps->size = 0;
	ps->capacity = INIT_CAPACITY;
}

//顺序表的销毁
void SLDestroy(SL* ps)
{
	assert(ps);
	free(ps->arr);//因为a是malloc出来的,所以free就要释放a
	ps->arr = NULL;//将a置为空指针
	ps->capacity = 0;
	ps->size = 0;
}


//顺序表的查满扩容
void SLCheckCapacity(SL* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * ps->capacity * 2);//扩容二倍比较合理
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->arr = tmp;//原地扩容
		ps->capacity *= 2;
	}
}


//顺序表的插入(尾插)
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);          //检查ps指针不为NULL

	SLCheckCapacity(ps);     //首先要检查容量,如果不够要增容

	ps->arr[ps->size] = x;    //最后将数据赋值给arr数组中的最后一个元素即可
	ps->size++;
}

//顺序表的删除(尾删)
void SLPopBack(SL* ps)
{
	assert(ps->size > 0);//括号里为真就过了,为假就报错

	ps->size--;
	//没必要把数据置为0,因为size减少就是无法访问了
	//其次是没法把删除的空间free掉,动态内存申请的空间都是"团购"的,要申请一起申请,要释放只能从头一起释放.
}


//顺序表的插入(头插)
//头插n个数据时间复杂度是O(n),少用!
void SLPushFront(SL* ps, SLDataType x)
{
	
	assert(ps);
	//插之前检查是不是满了
	SLCheckCapacity(ps);


	//先挪后放
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}

	ps->arr[0] = x;
	ps->size++;
}


//顺序表的删除(头删)删看空,插看满.时间复杂度O(n)
void SLPopFront(SL* ps)
{
	assert(ps);
	//判断是否为空,不为空才能删,为空直接报错
	assert(ps->size > 0);

	int begin = 1;
	while (begin < ps->size)
	{
		ps->arr[begin-1] = ps->arr[begin];
		begin++;
	}
	ps->size--;
}


//顺序表的定点插入
//有定点删除之后就可以不要头插尾插了,直接传参0或者size就行
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	//定点插入,插入点必须在0~size之间(判断).
	assert(pos >= 0 && pos <= ps->size);
	//pos==0,相当于头插,pos==ps->size,相当于尾插

	SLCheckCapacity(ps);

	int end = ps->size - 1;//从顺序表的末尾逐一向后挪动元素
	while (end >= pos)     //当将原本pos位置的元素挪走后,就可以插入新元素了
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	//插入新元素
	ps->arr[pos] = x;
	//元素个数增加
	ps->size++;
}


//顺序表的定点删除
//头删和尾删也不用写了
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//可以间接检查size是否为0.因为pos没法小于0.

	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->arr[begin - 1] = ps->arr[begin];
		begin++;
	}
	ps->size--;
}

//顺序表的查找
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;
}

//顺序表的打印
void SLPrint(SL* ps)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
	printf("打印成功:>\n");
}

SeqList.h文件

#define _CRT_SECURE_NO_WARNINGS 1

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>


typedef int SLDataType;//将结构体数组重命名,方便后面修改线性表的成员
#define INIT_CAPACITY 4


//动态顺序表——按需申请
typedef struct SeqList     //对结构体重命名为SL
{
	SLDataType *arr; //一个指针指向一片连续的空间
	int size;      //有效数据个数
	int capacity;  //空间容量
}SL;


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

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

//顺序表的查满
void SLCheckCapacity(SL* ps);

//顺序表的插入(尾插)
void SLPushBack(SL* ps, SLDataType x);
//顺序表的删除(尾删)
void SLPopBack(SL* ps);


//顺序表的插入(头插)
void SLPushFront(SL* ps, SLDataType x);
//顺序表的删除(头删)
void SLPopFront(SL* ps);


//顺序表的定点插入
void SLInsert(SL* ps, int pos, SLDataType x);
//顺序表的定点删除
void SLErase(SL* ps, int pos);


//顺序表的查找
int SLFind(SL* ps, SLDataType x);

//顺序表的打印
void SLPrint(SL* ps);

//顺序表的菜单
void SeqMenu(int size);

test.c文件

#include"SeqList.h"

int main()
{
    SL s;
    SLInit(&s);

    int swi = 0;//创建变量swi作为do...while循环的终止条件,以及switch语句的运行条件
    do          //使用do...while实现
    {
        SeqMenu(s.size);
        scanf("%d", &swi);
        
        switch (swi)
        {
        case 0:
            printf("您已退出程序:>\n");
            // 释放链表内存
            SLDestroy(&s);
            break;

        case 1:
            printf("请输入要插入的数据:>");
            int insert_data = 0;
            scanf("%d", &insert_data);
            printf("请输入要插入的数据位置:>");
            int insert_pos = 0;
            scanf("%d", &insert_pos);
            SLInsert(&s, insert_pos,insert_data);
            printf("已成功插入:>\n");
            break;

        case 2:
            printf("请输入要删除的数据位置:>");
            int delete_pos = 0;
            scanf("%d", &delete_pos);
            SLErase(&s, delete_pos);
            printf("删除成功:>\n");
            break;

        case 3:
            printf("请输入要查找的数据的值:>");
            int find_data = 0;
            scanf("%d", &find_data);
            int find_pos = SLFind(&s, find_data);
            if (find_pos != -1)
            {
                printf("找到了,数据元素%d的下标为%d\n", find_data, find_pos);
            }
            else
            {
                printf("没找到,顺序表中可能不存在此数据:<\n");
            }
            break;

        case 4:
            printf("打印数据:>\n");
            SLPrint(&s);
            break;

        case 5:
            printf("确定要销毁顺序表吗?:>\n");
            printf("销毁顺序表输入:1\n");
            printf("取消销毁顺序表输入:0\n");
            int destroy = 0;
            printf("请输入:>");
            scanf("%d", &destroy);
            if (destroy)
            {
                SLDestroy(&s);
                SLInit(&s);
                printf("已成功销毁:>\n");
            }
            else
            {
                printf("已取消销毁:>\n");
            }
            break;
        default:
            printf("输入错误,请重新输入\n");
            break;
        }
    } while (swi);

    return 0;
}

结语

希望这篇顺序表的实现详解能对大家有所帮助,欢迎大佬们留言或私信与我交流.

学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!

相关文章推荐

【数据结构】线性表的顺序存储结构

【C语言】memcpy()函数

【数据结构】什么是线性表?

【C语言】malloc()函数详解(动态内存开辟函数)

【C语言】free()函数详解(动态内存释放函数)

【C语言】realloc()函数详解(动态内存开辟函数)

【C语言实战项目】通讯录(动态增容版)

【C语言】memset()函数

【实用编程技巧】不想改bug?初学者必须学会使用的报错函数assert!(断言函数详解)



数据结构线性表篇思维导图:

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

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

相关文章

Pandas透视表

Pandas使用pivot_table()方法和crosstab()方法实现透视表。 pivot_table()方法及参数 pivot_table()方法的语法格式如下&#xff1a; pandas.pivot_table(data, valuesNone, indexNone, columnsNone, aggfuncmean, fill_valueNone, marginsFalse, dropnaTrue, margins_nameA…

10月份程序员书单推荐

新书书单 1、C程序设计教程&#xff08;第9版&#xff09; 1.广受认可的《C程序设计教程》系列的第9版&#xff08;个别版本也译作《C语言大学教程》&#xff09;&#xff0c;秉承了该系列一贯的丰富而详细的风格。该系列一些版本因封面画有蚂蚁形象而被称为“C语言蚂蚁书”。…

有一个带头结点的单链表L,设计一个算法使其元素递增有序

有一个带头结点的单链表L&#xff0c;设计一个算法使其元素递增有序 代码思路&#xff1a; 我这里懒得搞那个指针了&#xff0c;直接遍历一遍链表&#xff0c;把链表的元素复制到数组arr里面 对数组A进行一下排序&#xff0c;排完之后再把元素复制到L里面。 至于排序你用啥算…

构建外卖小程序:技术要点和实际代码

1. 前端开发 前端开发涉及用户界面设计和用户交互。HTML、CSS 和 JavaScript 是构建外卖小程序界面的主要技术。 <!-- HTML 结构示例 --> <header><h1>外卖小程序</h1><!-- 其他导航元素 --> </header> <main><!-- 菜单显示 -…

Capacitor 打包 h5 到 Android 应用,uniapp https http net::ERR_CLEARTEXT_NOT_PERMITTED

Capacitor 打包 h5 到 Android 应用&#xff0c;uniapp https http net::ERR_CLEARTEXT_NOT_PERMITTED capacitor 官网&#xff1a; https://capacitorjs.com/docs/ 项目上需要做一个 app&#xff0c;而这个 app 是用 uniapp 做的&#xff0c;里面用到了一个依赖 dom 的库&…

winscp显示隐藏文件

当前目录下有被隐藏的文件时&#xff0c;会在右下角看到 “已隐藏” 的字样&#xff0c;双击这个字样&#xff0c;就会显示被隐藏的文件和文件夹

私有云:【3】NFS存储服务器的安装

私有云&#xff1a;【3】NFS存储服务器的安装 1、使用vmwork创建虚拟机2、配置NFS服务器3、安装NFS存储服务4、配置NFS服务及创建存储共享 1、使用vmwork创建虚拟机 新建虚拟机NFS 分配400G硬盘&#xff0c;可以更高【用作存储】 自定义硬件 选择win2012的iso文件 设置登录密码…

数组与链表算法-矩阵算法

目录 数组与链表算法-矩阵算法 矩阵相加 C代码 矩阵相乘 C代码 转置矩阵 C代码 稀疏矩阵 C代码 数组与链表算法-矩阵算法 矩阵相加 矩阵的相加运算较为简单&#xff0c;前提是相加的两个矩阵对应的行数与列数必须相等&#xff0c;而相加后矩阵的行数与列数也是相同的。…

Notepad-- 轻量级文本编辑器的安装及基本使用

Notepad-- 轻量级文本编辑器的安装及基本使用 一、notepad--介绍1.1 notepad--简介1.2 notepad--特点 二、安装Notepad--2.1 下载Notepad--软件包2.2 解压软件包2.3 运行Notepad-- 三、Notepad-- 的日常使用3.1 编写yaml文件3.2 编写shell脚本文件3.3 编写python测试文件 四、文…

有关多线程环境下的Volatile、lock、Interlocked和Synchronized们

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;我们面对的不仅…

安装使用vcpkg的简易教程

目录 1. 首先安装vcpkg2. 在vcpkg目录下运行bootstrap-vcpkg.bat 命令3. 接着vs进行集成4. 使用vcpkg搜索可用的包5.下载安装所需包6.下载安装完成 1. 首先安装vcpkg 使用git命令下载 git clone https://github.com/Microsoft/vcpkg.git如果下载失败可直接下载文件 (vcpkg-ma…

美术如何创建 skybox 贴图资源?

文章目录 目的PS手绘Panorama To CubemapPS手绘Pano2VRSkybox & Cubemap Tutorial (Maya & Photoshop)Unity 中使用 ReflectionProbe 生成 Cubemap 然后再 PS 调整PS直接手绘 cubemapBlender 导入 Panorama&#xff0c;然后烘焙到 cubemap&#xff0c;再导入unity中使用…

git教程(1)---本地仓库操作

git教程 git安装-Centos基本操作git initgit config工作区和版本库工作区暂存区/索引版本库 添加文件---场景一git statusgit log查看.git目录结构 添加文件---场景二修改文件版本回退撤销修改场景一只有工作区有code工作区和暂存区有code所有区域都有code并且没有push到远程仓…

【Java网络原理】 五

本文主要介绍了TCP传输控制协议的报头字段意义以及TCP协议的十大核心特性。 一.TCP传输控制协议 1.TCP报文格式 >端口 范围是0-65535 &#xff0c;只有确定了端口号&#xff0c;才知道把数据报交给哪个应用程序。 >4位首部长度 TCP报头是变长的&#xff0c; 4bit的范…

Servlet核心API

目录 HttpServlet init destory service 实例&#xff1a;处理get、post、put、delete请求 1.通过postman得到请求 2.通过ajax得到请求 HttpServletRequest 常见方法 前端给后端传参 1.GET,query string 2.POST,form 3.POST&#xff0c;json HttpSeverletRespons…

数据类型与运算符-java

数据类型与运算符 1、变量和类型 1.1、整形变量 基本语法格式&#xff1a; int 变量名 初始值;代码示例&#xff1a; int num 10 //定义一个整型变量 System.out.println(num);注意&#xff1a; 1&#xff09;java中&#xff0c;一个int变量占4个字节&#xff0c;和操作…

【ChatGPT系列】ChatGPT:创新工具还是失业威胁?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

进程、线程、处理机调度

程序&#xff1a;存放在磁盘中的可执行二进制文件&#xff08;即*.exe文件&#xff0c;包含一系列指令集合&#xff09;。是静态的。 进程&#xff1a;程序的一次执行过程。是动态的。同一个程序多次执行将对应多个进程。 线程&#xff1a;轻量级进程。一个进程至少有一个线程…

qml之ui控件

文章目录 ui控件移动版风格嵌套页面并排界面 ui控件 Qt Quick控件用于创建由标准化组件&#xff08;如按钮、标签、滑块等&#xff09;构建的用户界面。 QtQuick.Controls&#xff1a;基本控件。QtQuick.Templates&#xff1a;为控件提供行为化的、非可化视的基本类型。QtQui…

STM32 CAN使用

STM32 CAN使用 简介各种通讯接口对比报文总线上的报文信息表示为几种固定的赖类型数据帧列表模式掩码模式配置CAN配置参数位时序 简介 控制器局域网CAN&#xff08;Controller Area Network)是由德国博世公司为汽车应用而开发的多主机局部网络&#xff0c;用于汽车的监测和控制…