顺序表基本操作<小白一听就懂!!!><超详细>&&<接地气>

本文以C语言实现顺序表的基本操作,包括初始化、增加元素、按序号和数值查找、插入、删除及逆置。文中深入浅出地解释了每个操作的原理和代码实现,特别强调了指针在函数参数传递中的作用,以及在处理数组时的一些细节,如遍历、插入和删除的注意事项。
摘要由CSDN通过智能技术生成

小编前言

主打一个浅显易懂 计科新生角度,分享对顺序表的理解,贴合初学者的思路分析讲解。
trust yourself,跟着小编的节奏,盘他!
闲言少叙,话不多少,发车!

完整代码

附详细注释

//顺序表中基本操作的实现,并会在主函数中调用这些基本操作:
//(1)创建顺序表,添加至少8个元素,并输出;
//(2)查找第5个元素值;
//(3)查找顺序表中是否存在99,若存在找出其位置;
//(4)在第5个位置插入100;
//(5)删除第4个位置的元素;
//(6)将顺序表中的元素逆置。

#include<stdio.h>
#define Maxlength 100
//定义结构体
typedef struct
{
	int data[Maxlength];
	int length;
}List;
void InitList(List* L);//初始化
void AddList(List* L);//增加元素
void PrintList(List L);//输出顺序表
int LocatSearch(List L, int i);//按序号查找
int DateSearch(List L, int e);//按照数值查找
void InserchList(List* L, int i, int e);//插入
void DeleteList(List* L, int i);//删除
void InverList(List* L, int Maxsize);//逆置
int LengthList(List L);//打印顺序表长度

#include"Head.h"
int main()
{
	int x;//存储按序号查找的返回值(返回的是数据的值)
	int i;//存储按照数值查找的返回值(返回的是数据的序号)
	int n;//储存线性表长度 b
	List L;
	InitList(&L);
	AddList(&L);
	PrintList(L);
	//按序号查找
	x= LocatSearch(L, 5);
	if (x != -1)
	{
		printf("\n第五个元素的值为:%d\n",x);
	}

	//按数值查找
	i = DateSearch(L, 99);
	if (i)
		printf("99在第%d个位置\n", i);
	else
		printf("99不存在\n");
	printf("\n在第5个位置插入100后为:\n");
	InserchList(&L, 5, 100);
	PrintList(L);
	DeleteList(&L, 4);
	n=LengthList(L);//确定顺序表的长度
	InverList(&L, n);//进行逆置操作
	printf("顺序表逆置之后为:\n");
	PrintList(L);
}
//初始化
void InitList(List *L)
{
	L->length = 0;
	return;
}
//增加元素
void AddList(List* L)
{
	int x;
	char select;
	do
	{	//即使开始初始化了但是仍然要判满,输入因为是一个循环
		if (L->length == Maxlength)
		{
			printf("Fuck!顺序表已满!");
			return;
		}
		printf("请输入要增加元素的值:");
		scanf_s("%d", &x);
		L->data[L->length++] = x;
		printf("是否继续添加?(Y/N)");
		scanf_s(" %c", &select);//加空格防止回车误触

	} while (select == 'y' || select=='Y');//逻辑或运算两边||都要有字符,不可一边有一边没有
}
//打印数据函数
void PrintList(List L)
{
	int i;
	for (i = 0; i <= L.length - 1; i++)
	{
		printf("(%d)%d\t", i + 1, L.data[i]);
	}
	printf("\n");
}
//按序号查找
int LocatSearch(List L, int i)
{ 
	//判断输入序号是否合法
	if (i<1 || i>L.length)
	{
		printf("您输入的数据非法!\n");
		return -1;
	}
	return L.data[i-1];//因为i是传入元素的序号,下标=序号-1
}
//按照数值查找
int DateSearch(List L, int e)
{
	int i;
	for (i = 0; i < L.length; i++)//遍历所有数组元素
	{
		if (L.data[i] == e)
		{
			return i + 1;
		}
	}
	return 0;
}

//插入元素
void InserchList(List* L, int i, int e)
{
	int j;
	//插入之前首先判满
	if (L->length == Maxlength)//注:=为赋值符号;而数学等号为==
	{
		printf("fuck!顺序表已满!\n");
		return;
	}
	//判断插入位置是否合法
	if (i<1 || i>L->length + 1)//length + 1因为可以在表的末尾加,也即是最后一个序号为length的元素后面
	{
		printf("插入位置错误!\n");
		return;
	}
	//插入位置之后的所有元素都要向后移动一个位置
	for (j = L->length - 1; j >= i - 1; j--)//j = l->length - 1将j初始化为顺序表中最后一个元素的下标
	{										//循环条件j >= i - 1保证了只有从待插入位置开始之后的所有元素才需要被移动
		L->data[j + 1] = L->data[j];//后移  //循环语句j--将j逐渐减小,
	}
	L->data[i - 1] = e;//插入e
	L->length++;//表的长度加一

	return;
}

//删除
void DeleteList(List* L, int i)
{
	//不用判空,因为在判断位置是否合法的时候就已经判断了是否是空的 
	//判断删除位置是否合法
	if (i<1 || i>L->length + 1)
	{
		printf("插入位置错误!\n");
		return;
	}
	int j;
	for (j = i; j < L->length; j++)
	{
		L->data[j - 1] = L->data[j];//前移
	}
	L->length--;//长度-1
	return;
}
//逆置
//在循环体中,temp变量用于暂存顺序表中第i个元素的值,
//然后将第i个元素和对应的最后一个元素进行交换
//即将第i个元素的值赋给l->data[Maxsize - 1 - i]
//Maxsize-1-i位置上的元素赋值为temp
void InverList(List* L, int Maxsize)//Maxsize为最大元素的序号
{
	int i;
	int temp;
	for (i = 0; i < Maxsize / 2;i++)
	{
		temp = L->data[i];
		L->data[i] = L->data[Maxsize - 1 - i];//Maxsize-1,为数组的下标,数组下标首尾进行交换
		L->data[Maxsize - 1 - i] = temp;
	}
	return;
}
//输出顺序表长度
int LengthList(List L)
{
	return L.length;
}

// 头文件

#include<stdio.h>
#define Maxlength 100  //  定义用于存储顺序表的数组
//  定义结构体
typedef struct
{
	int data[Maxlength];
	int length;  //  标示顺序表的最大长度
}List;  //  给typedef struct起一个“小名儿”叫List

void InitList(List* L);  //  初始化
void AddList(List* L);  //  增加元素
void PrintList(List L);  //  输出顺序表
int DateSearch(List L, int e);  //  按序号查找
int LocatSearch(List L, int i);  //  按照数值查找
void InserchList(List* L, int i, int e);  //  插入
void DeleteList(List* L, int i);  //  删除
void InverList(List* L, int Maxsize);  //  逆置
int LengthList(List L);  //  打印顺序表长度

//主函数

int main()
{
	int x;  //  存储按序号查找的返回值(返回的是数据的值)
	int i;  //  存储按照数值查找的返回值(返回的是数据的序号)
	int n;  //  储存线性表长度 b
	List L;  //  申请一个用结构体自定义类型的空间
	InitList(&L);
	AddList(&L);
	PrintList(L);
	//  按序号查找
	x= DateSearch(L, 5);
	if (x != -1)
	{
		printf("\n第五个元素的值为:%d\n",x);
	}

	//  按数值查找
	i = LocatSearch(L, 99);
	if (i)
		printf("99在第%d个位置\n", i);
	else
		printf("99不存在\n");
	//  插入
	printf("\n在第5个位置插入100后为:\n");
	InserchList(&L, 5, 100);
	PrintList(L);
	DeleteList(&L, 4);
	n=LengthList(L);  //  确定顺序表的长度
	InverList(&L, n);  //  进行逆置操作
	printf("顺序表逆置之后为:\n");
	PrintList(L);
}

// 初始化

void InitList(List *L)
{
	L->length = 0;  //  初始化,即令顺序表为空,也就是使表长为0
	return;
}
初始化目的

内存中为顺序表申请的这块空间,之前可能被使用过,因此其中存在一些残留的垃圾数值,初始化可对垃圾值进行清除,避免在操作时访问到未被初始化的元素导致程序崩溃。

核心思想:函数返回值类型为空,运用指针,通过形参表回传参数(L的length)

/运用指针/

那么问题来了:为什么要用指针?直接对length进行修改(L.length=0)不行嘛?

想要弄清楚这,还要从你学的清晰的一塌糊涂的C语言谈起:

C 语言中自定义函数的参数传递方式有两种:单向传递双向传递。其中,单向传递是指:将参数的值从调用函数(可理解为main函数)传递给被调用函数(自定义函数),而不会将被调用函数中变量值传递回调用函数。
简而言之,就是在自定义函数中,对主函数参数变量所做的修改是无效的。
这点小编是从两个方面去理解的:
1.函数内部变量独立被调用函数的变量和调用函数中的变量是独立的,它们有不同的内存地址和空间即使变量名相同它们也不是同一个变量,而是两个互相独立的变量。在被调用函数中,函数参数是局部变量,它会在函数执行结束后被自动销毁。因此,在被调用函数中不能使用参数的地址或引用,否则将会访问到无效的内存地址,引发程序错误。
2.只传递参数值:在单向传递中,只传递了参数的值,而没有传递参数的地址或引用。因此,在被调用函数中修改参数的值并不会影响调用函数中的参数值。

显而易见,直接对修改length的值(L.length=0)是错误的

初始化要对顺序表中元素(也就是主函数中的元素)进行修改,故要用指针改变L.length的地址,再通过形参表回传。

/空类型/

函数中不需要返回任何数值给主函数。
说到函数返回值的类型,我们不得不再复习一下清晰的一塌糊涂的C:

/理解函数-独家秘笈/

《独家干货秘笈理解》
关于黑盒模型:
黑盒顾名思义:它是一个不可视的容器。C语言的函数可看做是一个黑盒,它隐藏了函数内部的具体实现,只关注函数输入和输出的关系:输入参数作为黑盒的输入,函数的执行过程是黑盒内部的处理过程,输出结果作为黑盒的输出。

接地气一点,小编将函数看做一个方程式,(emm可能有点不严谨)向形参表中传参,执行函数体,返回值,==给方程中设的未知数赋值,带入方程,得出结果

int main()
{
	int a = 0;
	int b = 1;
	int c = 2;  //  (给方程未知数x,y赋值a,b也就是1,2)
	a = fun(b,c);  //  传入参数(带入赋值后的未知数)
	printf("%d", a);
	return 0;
}
int fun(int x, int y)  //  执行函数体(将未知数带入方程)
{
	int z;
	z = x - y;
	return z;  //  输出返回值(得出结果)
}

无论方程(函数)多么复杂,都是同样的解题步骤(运行方式)。

这样一来函数的嵌套也就很好解释了:无非就是将方程中的一个未知数看成一个式子

int fun(int x, int y)  //  传入参数(执行函数体)带入方程
{
	int z;
	z = x - y;  //  嵌套Max函数
	z = Max(x, z);  //  fun函数中的参数x为Max函数的值(原方程未知数z看成另一个方程式)
	return z;  //  输出返回值(得出结果)
}
int Max(int x, int y)
{
	return x > y ? x : y;
}
回归正题,顺序表

// 增加元素

void AddList(List* L)
{
	int x;
	char ch;
	do
	{	//  即使开始初始化了但是仍然要判满,输入因为不仅仅输入这一次而是要循环起来
		if (L->length == Maxlength)  //  若表长等于数组最大长度
		{
			printf("Fuck!顺序表已满!");  //  则顺序表已满
			return;
		}
		printf("请输入要增加元素的值:");
		scanf_s("%d", &x);
		L->data[L->length++] = x;  //  将接收的数据存入顺序表(数组),每存一个数表长+1(length++)
		printf("是否继续添加?(Y/N)");
		scanf_s(" %c", &select);  //  加空格防止程序将回车误读,而发生错误
	} while (ch == 'y' || ch=='Y');  //  逻辑或运算两边||都要有字符,不可一边有一边没有
}
核心思想:1.插入操作要对顺序表进行修改,因此用指针类型,在形参表中回传参数
		2.插入前首先判满
        3.接收添加的元素并存入数组
        4.接收循环判断字符
        5.最外层用while循环套上,让其循环起来

/i++与++i/

i++是先用i进行运算,再进行+1操作
++1是先对i进行+1操作,再进行运算

// 打印顺序表

void PrintList(List L)
{
	int i;
	for (i = 0; i <= L.length - 1; i++)  //  for循环遍历数组
	{
		printf("(%d)%d\t", i + 1, L.data[i]);
	}
	printf("\n");
}
核心思想:顺序表存于数组内,输出顺序表即输出数组元素(用for循环遍历)
		注:打印仅仅是输出顺序表中内容,并不对表进行修改,可不用指针类型(当然用也无妨)

c语言一个函数只能返回一个返回值,so不要尝试用返回值打印 (小编开始是这么想的)

/遍历数组循环条件的两种表达/

i <= L.length - 1;
i < L.length;
二者都是将i遍历到数组的最大下标

// 按序号查找

//按序号查找
int DateSearch(List L, int e)
{ 
	//判断输入序号是否合法
	if (i<1 || i>L.length)  //  i为序号,合法范围在1-length之间
	{
		printf("您输入的数据非法!\n");
		return -1;
	}
	return L.data[i-1];//因为i是传入元素的序号,下标=序号-1
}
核心思想:1.按照序号查找,返回的是元素的值,函数返回值类型为数组中数据元素的类型
		2.首先判断输入的序号是否合法
		3.若输入的元素经过检测,则返回数组元素下标为[i-1]位置的值(i为序号,数组是从下标为0的位置存的第一个元素)
		4.若未经过检测,则返回一个顺序表中不可能出现的数(例如-1),用于主函数书否输出返回值的判断条件

/数组序号与下标关系/

小编将其分为两种类型:
下标为0的位置开始存 数据:初学常用,基础且易理解的一种方式。数组下标=元素序号-1,如此也导致了,操作时的不便。
下标为1的位置开始存 数据:将data[0]的位置空出来,这种思想,在数据结构查找和排序章节涉及。data[0]空位:可设置监视哨,可用作为临时变量(temp),辅助数据的交换,同时使元素序号和数组下标一致。

// 按照数值查找

int LocatSearch(List L, int i)
{
	int i;
	for (i = 0; i < L.length; i++)//遍历所有数组元素
	{
		if (L.data[i] == e)
		{
			return i + 1;
		}
	}
	return 0;
}
核心思想:1.按数值查找,返回的是元素的序号,返回值类型为整型
		2.(此处使用最基础的线性查找方式),从一端开始逐个比较查找,直到找到目标数据或查找到整个数据集为止,时间复杂度为O(n)。

注:还可使用其他查找方法(数据结构后半部分会讲到)

// 插入

//插入元素
void InserchList(List* L, int i, int e)
{
	int j;
	//插入之前首先判满
	if (L->length == Maxlength)//注:=为赋值符号;而数学等号为==
	{
		printf("fuck!顺序表已满!\n");
		return;
	}
	//判断插入位置是否合法
	if (i<1 || i>L->length + 1)//length + 1因为可以在表的末尾加,也即是最后一个序号为length的元素后面
	{
		printf("插入位置错误!\n");
		return;
	}
	//插入位置之后的所有元素都要向后移动一个位置
	for (j = L->length - 1; j >= i - 1; j--)//j = L->length - 1将j初始化为顺序表中最后一个元素的下标
	{										//循环条件j >= i - 1保证了只有从待插入位置开始之后的所有元素才需要被移动
		L->data[j + 1] = L->data[j];//后移  //循环语句j--将j逐渐减小,
	}
	L->data[i - 1] = e;//插入e
	L->length++;//表的长度加一

	return;
}

乍一看,代码很复杂,实则是前面AddList,DateSearch思想的结合

核心思想:1.插入相当于特定位置的增加,开始操作前毋庸置疑的判满
		2.你要告诉程序,在顺序表的什么位置,插入数值为多大的元素。所以需要,插入位置,插入数据元素的值,顺序表L三个参数,
		3.判断插入位置是否合法(同DateSearch思想)
		4.顺序表中元素是一个接一个有序排列,插入就跟排队时加塞一样(如下图),从要插入位置到最后一个元素,每个都要后移一位
		5.存入元素
		6.表长+1

/后移操作/

若如实际插队一样从前向后挨个后移,L->data[j + 1] = L->data[j];就会发现,最后一个元素被倒数第二个覆盖掉了,为了避免这一情况,从最后一个元素向开始后移才是正解。

// 删除

void DeleteList(List* L, int i)
{
	//不用判空,因为在判断位置是否合法的时候就已经判断了是否是空的 
	//判断删除位置是否合法
	if (i<1 || i>L->length + 1)
	{
		printf("插入位置错误!\n");
		return;
	}
	int j;  //  将插入位置进行后移操作
	for (j = i; j < L->length; j++)
	{
		L->data[j - 1] = L->data[j];//前移
	}
	L->length--;//长度-1
	return;
}
核心思想:1.判断删除位置是符合法(核心同按序号查找)
		2.元素前移
		3.表长-1

/无需判满/

添加要判满,但删除不用判空。在判断位置是否合法时,就已经判断了是否是空的。顺序表为空,length为0,带入if (i<1 || i>l->length + 1),条件成立,直接返回空,不会进入接下来的循环。

/前移/。

与后移思想相同,为了数据不被覆盖,要从删除位置后一个元素开始前移

// 逆置

//逆置
void InverList(List* L, int Maxsize)//Maxsize为最大元素的序号
{
	int i;
	int temp;
	for (i = 0; i < Maxsize / 2;i++)
	{
		temp = L->data[i];
		L->data[i] = L->data[Maxsize - 1 - i];//Maxsize-1,为数组的下标,数组下标首尾进行交换
		L->data[Maxsize - 1 - i] = temp;
	}
	return;
}
核心思想:相当于两个数做交换1.在循环体中,temp变量暂存顺序表中第i个元素的值
		2.然后将第i个元素和对应的最后一个元素进行交换,即将第i个元素的值赋给L->data[Maxsize - 1 - i]
		3.Maxsize-1-i位置上的元素赋值为temp,完成了两个位置上元素的交换操作

// 打印顺序表长度

int LengthList(List L)
{
	return L.length;
}
核心思想:1.可直接打印length
		2.此处利用面向对象的封装思想,将其封装成函数

//----------------------------------------帅哥美女都点这里----------------------------------------

好吧,这是一段结束语,

以上就是小编对顺序表的理解,不同于教材以及其他参考代码的是小编结合自己学习中出现的问题,以贴近初学者的思维进行剖析,希望能对大家有所帮助。今后小编会随着学习进度,逐步更新内容,(计划上线完整的数据结构与C语言—小白式教学)希望大家多多支持,我们一起提高,共同成长。

创作不易,请看官老爷们给小编点一个赞吧

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值