发布时间:2025-06-24 17:57:44  作者:北方职教升学中心  阅读量:213


尾删\n"); printf("7、

=========================================================================

相关代码gitee自取

C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

【数据结构初阶】一. 复杂度讲解_高高的胖子的博客-CSDN博客

 =========================================================================

                      

1 . 线性表

               

线性表linear list)是n个具有相同特性的数据元素的有限序列

链表示例:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

2 . 顺序表

顺序表概念及结构:

               

顺序表一段物理地址连续的存储单元依次存储数据元素线性结构

一般情况下采用数组存储

数组上完成数据的增删查改

顺序表一般可以分为静态顺序表动态顺序表

               

               

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

                

因为静态顺序表使用定长数组存储元素

对空间的运用不够灵活,可能造成空间浪费或不够的问题
所以在实际情况下静态顺序表并不常用不够实用

示例:

                    

                    

动态顺序表:使用动态开辟的数组存储元素

             

静态顺序表适用于确定知道需要存多少数据的场景。打印 -1、

静态顺序表定长数组导致N定大了空间开多了浪费开少了不够用字符串...

顺序表示例:

              

              

线性表逻辑上线性结构,也就说是连续的一条直线

所以现实中基本都是使用动态顺序表根据需要动态地分配空间大小

示例:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

3 .接口实现(实现动态顺序表):

(详细解释在注释,代码分文件放最后)

                

数据结构可以管理数据

通过增删改查等操作就可以实现管理数据

              

现在有了动态顺序表后

就可以对其进行增删改查等操作实现动态顺序表

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLInit函数 -- 顺序表初始化

                      

                      

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLDestroy函数 -- 顺序表销毁

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLPrint函数 -- 测试函数

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLPushBack函数 -- 将值插入顺序表尾部(尾插)

尾插函数SLPushBack测试:

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

上面尾插需要考虑空间不够进行扩容

后面的头插同样也需要考虑

所以可以在顺序表实现文件设置一个内部函数SLCheckCapacity,

在需要的时候直接调用该函数进行扩容操作

↓↓↓↓↓

                 

SLCheckCapacity内部函数 -- 进行扩容操作

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLPopBack函数 -- 将值从顺序表尾部删除(尾删)

尾删函数SLPopBack测试:

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLPushFront函数 -- 将值插入顺序表头部(头插)

头插函数SLPushFront测试:

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLPopFront函数 -- 将值从顺序表头部删除(头删)

头删函数SLPopFront测试:

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLInsert函数 -- 在指定位置(pos)插入想要的值(x)

指定添加函数SLInsert测试:

             

这里写了指定位置插入SLInsert函数

可以想到其实 头插 尾插也是可以用 SLInsert函数 实现的

所以可以在头插和尾插函数中复用 SLInsert函数减少代码量

↓↓↓↓↓

                  

复用SLInsert函数 -- 改写头插函数SLPushFront

                     

复用SLInsert函数 -- 改写尾插函数SLPushBack

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

上面SLInsert函数涉及了插入位置pos下标

有时在增删改查操作时,需要知道有效元素中某个元素的下标再进行操作,

所以我们可以定义一个函数SLFind查找该元素的下标

↓↓↓↓↓

                 

SLFind函数 -- 查找x这个值在顺序表中的下标是多少

查找函数SLFind测试:

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLErase函数 -- 删除指定位置(pos)的值

删除指定位置函数SLErase测试:

                   

完成指定删除SLErase函数

头删 尾删 函数中也可以进行复用 SLErase函数 减少代码量

↓↓↓↓↓

                  

用SLErase函数 -- 改写头删函数SLPopFront

                     

用SLErase函数-- 改写尾删函数SLPopBack

                      

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SLModify函数 -- 把某个位置(pos)的值修改为某值(x)

修改函数SLModify测试:

          

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

4 . 对应代码

SeqList.h -- 顺序表头文件

#pragma once定义一个静态顺序表:使用定长数组存储元素因为静态顺序表使用定长数组存储元素,对空间的运用不够灵活所以在实际情况下,静态顺序表并不常用(不够实用)////#define N 1000  //“表”的大小的值//typedef int SLDataType;  //定义顺序表中存储的类型,这里是int类型////struct SeqList//{//	SLDataType a[N]; //定义表的大小//	int size; //记录表中存储了多少个有效数据//};//将需要的头文件都包含在 SeqList.h 头文件中#include <stdio.h>#include <stdlib.h>#include <assert.h>//定义一个动态顺序表:使用动态开辟的数组存储元素typedef int SLDataType;  //定义顺序表中存储的类型,这里是int类型typedef struct SeqList{	//定义一个顺序表类型的指针,指向动态开辟的数组	SLDataType* a; 		int size; //记录表中存储了多少个有效数据	int capactity; //容量空间的大小}SL;//数据结构 --> 管理数据 --> 增删查改//顺序表初始化  --  头文件中声明void SLInit(SL* ps); //顺序表销毁 --  头文件中声明//内存是动态开辟地,不销毁的话可能会导致内存泄漏)void SLDestroy(SL* ps);//写一个测试函数(声明),方便检查各步骤有没有问题:void SLPrint(SL* ps);//尾插(声明) -- 将值插入顺序表尾部:void SLPushBack(SL* ps, SLDataType x);//尾删(声明) -- 将值从顺序表尾部删除:void SLPopBack(SL* ps);//头插(声明) -- 将值插入顺序表头部:void SLPushFront(SL* ps, SLDataType x);//头删(声明) -- 将值从顺序表头部删除:void SLPopFront(SL* ps);//在指定位置(pos)插入想要的值(x)void SLInsert(SL* ps, int pos, SLDataType x);//查找x这个值在顺序表中的下标是多少:int SLFind(SL* ps, SLDataType x); //返回找到的下标//删除指定位置(pos)的值:void SLErase(SL* ps, int pos);//把某个位置(pos)的值修改为某值(x)void SLModify(SL* ps, int pos, SLDataType x);

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

SeqList.c -- 顺序表实现文件

#define _CRT_SECURE_NO_WARNINGS 1//包含我们写的 SeqList.h 头文件#include "SeqList.h"//顺序表初始化 -- 实现void SLInit(SL* ps){	//assert断言,防止接收空指针:	assert(ps);	//初始化顺序表类型指针	//初始化时要先开辟一些动态空间:	//开辟的空间为顺序表类型,大小为4个顺序表类型的大小	ps->a = (SLDataType*)malloc(sizeof(SLDataType)*4); 		//顺序表作为一个独立的程序,之后可能会应用于其他程序,	//所以要对开辟的动态空间进行检查:	if (ps->a == NULL)	{		//可能开辟的空间过大 或 前面开辟太多空间不够再开辟		perror("malloc failed");  //打印错误		//让程序以异常的形式结束程序		//和 return 不同,return后主函数还会继续进行		exit(-1);	}	//将顺序表中的有效数据初始化为0:	ps->size = 0;	//将已动态开辟的容量空间初始化为4:	ps->capactity = 4;}//顺序表销毁 -- 实现//内存是动态开辟地,不销毁的话可能会导致内存泄漏)void SLDestroy(SL* ps){	//assert断言,防止接收空指针:	assert(ps);	//释放前面开辟的动态空间:	free(ps->a);		//将释放的指针置为空指针:	ps->a = NULL;	//将已开辟的动态空间置为0,顺序表有效数据也置为0	ps->capactity = ps->size = 0;}//写一个测试函数(实现),方便检查各步骤有没有问题:void SLPrint(SL* ps){	//assert断言,防止接收空指针:	assert(ps);	//打印动态顺序表现有的有效数据:	for (int i = 0; i < ps->size; i++)	{		printf("%d ", ps->a[i]);	}	//打印完当前动态顺序表有效数据后进行换行:	printf("\n");}//在顺序表实现文件中设置一个内部函数SLCheckCapacity,//在需要的时候直接调用该函数进行扩容操作void SLCheckCapacity(SL* ps){	//assert断言,防止接收空指针:	assert(ps);	//判断开辟的空间是否已满,满了再开辟:	if (ps->size == ps->capactity)		//顺序表有效个数 等于 已开辟容量空间	{		//使用 realloc函数 进行扩容,每次扩容2倍:		SLDataType* tmp = (SLDataType*)realloc(ps->a, ps->capactity * 2 * (sizeof(SLDataType)));		// realloc 是把空间扩容到第二个参数的大小,而不是直接把空间扩容第二个参数的大小		//同样进行检验:		if (tmp == NULL)		{			//打印错误:			perror("realloc failed");			//出现错误直接终止程序:			exit(-1);		}		//realloc函数,有两种扩容情况:一种是原地扩容,另一种是异地扩容		//原地扩容:扩容时看原空间后的内容有没有分配给别人,如果没有则把原空间后面的空间标记出来进行扩容,返回原空间的地址		//异地扩容:如果扩容时原空间后面的内容已被分配,则另找一个足够原空间扩容后的空间进行存放,		//		   原本的数据都搬到这个空间来,原空间会被释放,返回这个新空间的地址。链表、退出\n");	printf("

**\n");}int main(){ //先创建一个顺序表: SL sl; //再对其进行初始化: SLInit(&sl); //创建一个变量接收菜单选项: int option = 0; do { //使用菜单: menu(); //打印提示信息: printf("请选择想要进行的操作的序号:>"); //接收序号: scanf("%d", &option); printf("\n"); if (option == 1) { printf("请依次输入你要插入的数据个数:>"); int n = 0; //接收数据个数 scanf("%d", &n); //接收数据个数 printf("\n"); printf("请依次输入你要插入的数据\n"); //知道数据个数后,直接使用for循环循环接收数据 int x = 0; for (int i = 0; i < n; i++) { scanf("%d", &x); SLPushBack(&sl, x); } } else if (option == 7) { SLPrint(&sl); } } while (option != -1); //最后销毁顺序表: SLDestroy(&sl); //TestSeqList1(); //TestSeqList2(); //TestSeqList3(); //TestSeqList4(); //TestSeqList5(); //TestSeqList6(); //TestSeqList7(); return 0;}
尾插 2、

但是在物理结构上并不一定是连续的

线性表物理上存储时,通常以数组链式结构形式存储。 //所以使用 realloc函数 扩容后,不要对原空间指针进行释放, //如果realloc执行后是原地扩容返回原空间指针,扩容后后对原空间指针释放就会出问题 //把扩容返回的指针赋给原指针: ps->a = tmp; //将已动态开辟的容量空间置为原来的2倍: ps->capactity *= 2; }}//尾插(实现) -- 将值插入顺序表尾部:void SLPushBack(SL* ps, SLDataType x){ //assert断言,防止接收空指针: assert(ps); 调用内部函数进行扩容操作: //SLCheckCapacity(ps); 空间容量足够后将要插入尾部的值插入: //ps->a[ps->size] = x; 因为下标从0开始,所以size的值会等于现有元素下一位的下标 //ps->size++; //尾部插入元素后,更新顺序表中有效数据个数 //复用SLInsert函数 -- 改写尾插函数SLPushBack SLInsert(ps, ps->size, x); //在size位置插入就相当于尾插}//尾删(实现) -- 将值从顺序表尾部删除:void SLPopBack(SL* ps){ //assert断言,防止接收空指针: assert(ps); 检查一下:如果有效数据size等于0,则不能继续删除了 防止越界,可能一直删除到空间外了导致越界 // 检查方法一:太“温柔”,返回后不知道自己错了 if (ps->size == 0) { return; //直接返回 即可 } // 检查方法二:使用断言,错误了会直接报错并给出错误信息 //assert(ps->size > 0); //顺序表中有效数据大于0才进行下面的操作 直接有效数据size--即可 尾插是也是用size插入的,所以下次添加时直接就覆盖了原来值 //ps->size--; 不能局部释放,不能说不要末尾的值就把末尾的这个空间直接释放掉 C++有个规定,malloc一次就要free一次,要free释放的话只能整体释放 顺序表的物理内存是连续的,释放空间是不能“分期”的 //复用SLErase函数 -- 改写尾删函数SLPopBack SLErase(ps, ps->size-1); //size-1 就是最末元素下标}//头插(实现) -- 将值插入顺序表头部:void SLPushFront(SL* ps, SLDataType x){ //assert断言,防止接收空指针: assert(ps); 调用内部函数进行扩容操作: //SLCheckCapacity(ps); 将现有有效数据往后移一位,让出头位置: //int end = ps->size - 1; size-1 有效个数-1,就相当于当前最后一个元素的下标 //while (end >= 0) // //使用while循环循环移动各个有效元素,一直移到下标为0的元素 //{ // ps->a[end + 1] = ps->a[end]; //end下标元素 移到 下一位上 // --end; //改变下标 //} 将要插入头部的元素x插入头部: //ps->a[0] = x; 有效个数加一: //ps->size++; Capacity在扩容函数SLCheckCapacity中就修改好了 //复用SLInsert函数 -- 改写头插函数SLPushFront SLInsert(ps, 0, x); //在0位置插入就相当于头插}//头删(实现) -- 将值从顺序表头部删除:void SLPopFront(SL* ps){ //assert断言,防止接收空指针: assert(ps); 因为要将头部的有效元素删除, 所以直接把第一个有效元素后面的其他元素往前移一位就行了, 覆盖掉第一个元素 先进行断言检查,防止没有元素还进行删除: //assert(ps->size > 0); //int begin = 1; //从下标为1的元素开始往前移,把下标为0的元素覆盖 //while (begin < ps->size) // //只要begin还小于有效元素个数就继续往前移 // //因为是从下标为1的元素开始移动, // //所以最后就只有下标为0的元素没动 //{ // //把当前begin下标的元素往前挪一位: // ps->a[begin - 1] = ps->a[begin]; // //当前begin下标元素移动后,begin++继续移动下一位: // ++begin; //} // 因为第一个元素被覆盖,所以有效元素size-- //ps->size--; //复用SLErase函数 -- 改写头删函数SLPopFront SLErase(ps, 0); //头元素下标是0}//在指定位置(pos)插入想要的值(x)void SLInsert(SL* ps, int pos, SLDataType x){ //先使用assert检查pos是否合法: //在pos位置插入一个值后,顺序表还得是连续的 assert(pos >= 0 && pos <= ps->size); //pos等于0 -- 相等于头插 //pos等于size -- 相等于尾插 //使用SLCheckCapacity进行扩容操作: SLCheckCapacity(ps); //定义一个变量end,对应要移动元素的下标 int end = ps->size - 1; //一开始对应最后一个元素的下标 while (end >= pos) //只要end下标对应的元素还在pos下标元素的右边 //就把“end元素”向右移,移到pos下标无元素 { //把“end元素”向右移 ps->a[end + 1] = ps->a[end]; //移动下标进行写一个元素的移动 --end; } //让出位置后,将接收的x插入pos位置: ps->a[pos] = x; //有效元素+1: ps->size++;}//查找x这个值在顺序表中的下标是多少:int SLFind(SL* ps, SLDataType x) //返回找到的下标{ //assert断言,防止接收空指针: assert(ps); //数据量不大的话直接暴力查找吧: for (int i = 0; i < ps->size; i++) //有几个有效元素就循环几次: { if (ps->a[i] == x) //该下标元素等于要找的值x则返回当前下标: { return i; } } return -1; //表未找到该元素下标}//删除指定位置(pos)的值:void SLErase(SL* ps, int pos){ //同样先使用assert检查pos是否合法: //这里检查条件pos不能像SLInsert一样等于size //因为size空了能插入(尾插),但不能删除 assert(pos >= 0 && pos < ps->size); //指定删除和头删的思路类似, //只要把pos后面的值往前覆盖,覆盖掉pos的值就好了: int begin = pos + 1; //从pos+1的位置往前挪 while (begin < ps->size) //一直移到size为止(不包括size位置) { ps->a[begin - 1] = ps->a[begin]; //往前挪 ++begin; //挪完继续挪下一个 } ps->size--; //覆盖掉pos位置的值后,有效数字减一}//把pos位置的值修改为xvoid SLModify(SL* ps, int pos, SLDataType x){ //同样先使用assert断言检查pos是否合法: assert(pos >= 0 && pos < ps->size); //进行修改: ps->a[pos] = x;}

                        

--------------------------------------------------------------------------------------------------------------------------

                  

                      

test.c -- 测试函数

#define _CRT_SECURE_NO_WARNINGS 1//包含我们写的 SeqList.h 头文件#include "SeqList.h"//分组测试,测试不同的函数--SLPushBack 和 SLPopBack 函数void TestSeqList1(){	SL sl;  //创建顺序表类型变量	//使用 SLInit函数 初始化顺序表类型变量	//注意传递的是变量的地址,防止形参改变实参不改变	SLInit(&sl);	//使用尾插函数SLPushBack:	SLPushBack(&sl, 1);	SLPushBack(&sl, 2);	SLPushBack(&sl, 3);	SLPushBack(&sl, 4);	//此时再调用会进行扩容:	SLPushBack(&sl, 5);	SLPushBack(&sl, 6);	//调用测试函数SPrint查看是否插入成功:	SLPrint(&sl);	//测试尾删函数SLPopBack:	SLPopBack(&sl);	SLPopBack(&sl);	//调用测试函数SPrint查看是否“删除”成功:	SLPrint(&sl);	//测试完后使用SLDestroy函数销毁顺序表:	SLDestroy(&sl);}//分组测试,测试不同的函数--SLPushFront函数void TestSeqList2(){	SL sl;  //创建顺序表类型变量	//使用 SLInit函数 初始化顺序表类型变量	//注意传递的是变量的地址,防止形参改变实参不改变	SLInit(&sl);	//使用尾插函数SLPushBack:	SLPushBack(&sl, 1);	SLPushBack(&sl, 2);	SLPushBack(&sl, 3);	SLPushBack(&sl, 4);	//此时再调用会进行扩容:	SLPushBack(&sl, 5);	SLPushBack(&sl, 6);	//调用测试函数SPrint查看是否插入成功:	SLPrint(&sl);	//测试头插函数SLPushFront:	SLPushFront(&sl, 10);	SLPushFront(&sl, 20);	//调用测试函数SPrint查看是否“删除”成功:	SLPrint(&sl);	//测试完后使用SLDestroy函数销毁顺序表:	SLDestroy(&sl);}//分组测试,测试不同的函数--SLPopFront函数void TestSeqList3(){	SL sl;  //创建顺序表类型变量	//使用 SLInit函数 初始化顺序表类型变量	//注意传递的是变量的地址,防止形参改变实参不改变	SLInit(&sl);	//使用尾插函数SLPushBack:	SLPushBack(&sl, 1);	SLPushBack(&sl, 2);	SLPushBack(&sl, 3);	SLPushBack(&sl, 4);	//此时再调用会进行扩容:	SLPushBack(&sl, 5);	SLPushBack(&sl, 6);	//调用测试函数SPrint查看是否插入成功:	SLPrint(&sl);	//测试头删函数SLPopFront:	SLPopFront(&sl);	SLPopFront(&sl);	//调用测试函数SPrint查看是否“删除”成功:	SLPrint(&sl);	//测试完后使用SLDestroy函数销毁顺序表:	SLDestroy(&sl);}//分组测试,测试不同的函数--SLInsert函数void TestSeqList4(){	SL sl;  //创建顺序表类型变量	//使用 SLInit函数 初始化顺序表类型变量	//注意传递的是变量的地址,防止形参改变实参不改变	SLInit(&sl);	//使用尾插函数SLPushBack:	SLPushBack(&sl, 1);	SLPushBack(&sl, 2);	SLPushBack(&sl, 3);	SLPushBack(&sl, 4);	//此时再调用会进行扩容:	SLPushBack(&sl, 5);	SLPushBack(&sl, 6);	//调用测试函数SPrint查看是否插入成功:	SLPrint(&sl);	//测试指定增加函数SLInsert:	SLInsert(&sl, 2, 100);	//调用测试函数SPrint查看是否“删除”成功:	SLPrint(&sl);	//int x;	//scanf("%d", &x);	//int pos = SLFind(&sl, x);	//if (pos != -1)	//{	//	SLInsert(&sl, pos, x * 10);	//}	//SLPrint(&sl);	//测试完后使用SLDestroy函数销毁顺序表:	SLDestroy(&sl);}//分组测试,测试不同的函数--SLFind函数void TestSeqList5(){	SL sl;  //创建顺序表类型变量	//使用 SLInit函数 初始化顺序表类型变量	//注意传递的是变量的地址,防止形参改变实参不改变	SLInit(&sl);	//使用尾插函数SLPushBack:	SLPushBack(&sl, 1);	SLPushBack(&sl, 2);	SLPushBack(&sl, 3);	SLPushBack(&sl, 4);	//此时再调用会进行扩容:	SLPushBack(&sl, 5);	SLPushBack(&sl, 6);	//调用测试函数SPrint查看是否插入成功:	SLPrint(&sl);	//测试指定增加函数SLFind:	int pos = SLFind(&sl, 2);		printf("2在元素中是第%d个元素", pos+1);	//测试完后使用SLDestroy函数销毁顺序表:	SLDestroy(&sl);}//分组测试,测试不同的函数--SLErase函数void TestSeqList6(){	SL sl;  //创建顺序表类型变量	//使用 SLInit函数 初始化顺序表类型变量	//注意传递的是变量的地址,防止形参改变实参不改变	SLInit(&sl);	//使用尾插函数SLPushBack:	SLPushBack(&sl, 1);	SLPushBack(&sl, 2);	SLPushBack(&sl, 3);	SLPushBack(&sl, 4);	//此时再调用会进行扩容:	SLPushBack(&sl, 5);	SLPushBack(&sl, 6);	//调用测试函数SPrint查看是否插入成功:	SLPrint(&sl);	int x;	scanf("%d", &x);	//配合SLFind函数,找到顺序表中某个值的下标	int pos = SLFind(&sl, x);	//再使用SLErase函数通过下标删除该值	if (pos != -1)	{		SLErase(&sl, pos);	}	SLPrint(&sl);	//测试完后使用SLDestroy函数销毁顺序表:	SLDestroy(&sl);}//分组测试,测试不同的函数--SLModify函数void TestSeqList7(){	SL sl;  //创建顺序表类型变量	//使用 SLInit函数 初始化顺序表类型变量	//注意传递的是变量的地址,防止形参改变实参不改变	SLInit(&sl);	//使用尾插函数SLPushBack:	SLPushBack(&sl, 1);	SLPushBack(&sl, 2);	SLPushBack(&sl, 3);	SLPushBack(&sl, 4);	//此时再调用会进行扩容:	SLPushBack(&sl, 5);	SLPushBack(&sl, 6);	//调用测试函数SPrint查看是否插入成功:	SLPrint(&sl);		//测试指定增加函数SLInsert:	SLModify(&sl, 2, 100);	//调用测试函数SPrint查看是否“删除”成功:	SLPrint(&sl);	//测试完后使用SLDestroy函数销毁顺序表:	SLDestroy(&sl);}//菜单:void menu(){	printf("

**\n"); printf("1、队列、头插\n"); printf("3、

线性表是一种在实际中广泛使用的数据结构

常见线性表顺序表、头删 4、