顺序表基本操作<小白一听就懂!!!><超详细>&&<接地气>
小编前言
主打一个浅显易懂 计科新生角度,分享对顺序表的理解,贴合初学者的思路分析讲解。
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语言—小白式教学)希望大家多多支持,我们一起提高,共同成长。
创作不易,请看官老爷们给小编点一个赞吧