一 线性表的基本概念
线性表,顾名思义便是排列成一条线的序列,可以想象成被串在一根竹签上的一串糖葫芦球。
如上图所示,一串糖葫芦便是一个线性表,而其中每个糖葫芦球都是线性表中的元素。
同理,一串烤土豆片也是一个线性表,其中每片土豆都是表中的一个元素。
就是这样,关于线性表的基本概念和定义,我们不需要记得太学术,直接这么简单粗暴地理解即可。
好了,放个毒提提神,进入正题。
二 顺序表的定义
顺序表,即线性表在内存空间中的连续存储,逻辑上相邻的两元素,在物理地址上也是相邻的。
例如,我们在编程中所常用到的数组,便是一种典型的顺序表。
顺序表的第一种定义方式,称为静态分配,C语言代码如下:
#include<stdlib.h>
#define MAXSIZE 100
typedef struct {
double Data[MAXSIZE]; //为顺序表分配连续的存储空间
int length; //记录顺序表当前表长
} SeqList; //静态分配顺序表,最大表长为MAXSIZE
int main(void) {
SeqList SqList; //定义结构体变量SqList,同时定义了顺序表
//注意:变量命名应尽量遵循见名之意的原则,比如在这里,像“SeqList L;”这样的格式不可取
SqList.length = 0; //顺序表初始状态为空表,故表长初始化为0
return 0;
}
上述程序中,以double类型作为顺序表中元素的数据类型。
静态分配顺序表的变量结构如下图所示:
由于静态分配的顺序表在表长达到上限后,便无法再插入元素,因此,为了能够灵活扩充表长,我们更多采用动态分配的方式定义顺序表,C语言代码如下:
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
int main(void) {
SeqList SqList; //定义结构体变量SqList
SqList.Data = (double*)malloc(sizeof(double) * MAXSIZE);
//动态分配顺序表的存储空间,初始最大表长设为MAXSIZE
if (!SqList.Data) {
printf("Allocation Failure!\n");
exit(EXIT_FAILURE);
} //若动态分配连续存储空间失败,即返回给SqList.Data的是一个空指针,则退出程序
return 0;
}
动态分配顺序表的变量结构如下图所示:
以下,我们在动态分配的基础上,讨论顺序表的基本操作。
三 顺序表的基本操作
线性表常见的基本操作如下:
①初始化;
②求表长;
③插入操作;
④删除操作;
⑤按位查找;
⑥按值查找;
⑦输出操作;
⑧判空操作;
⑨销毁操作
下面我们一一讨论上述列举的九种基本操作。
①初始化
顺序表的初始化,即构造一个空的顺序表,并将初始表长length置为0,初始最大表长MaxSize置为常量MAXSIZE。
这里,我们在上一个部分所给出的程序基础上,稍作改动,即顺序表空间的动态分配交由初始化操作的函数InitList()来完成,而非在main函数中完成。
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList; //包含了顺序表首地址和表长信息的结构体
void InitList(SeqList* sequence) {
sequence->Data = (double*)malloc(sizeof(double) * MAXSIZE);
//动态分配顺序表的存储空间,初始最大表长设为MAXSIZE
if (!sequence->Data) {
printf("Allocation Failure!\n");
exit(EXIT_FAILURE);
} //若动态分配连续存储空间失败,即返回给sequence->Data的是一个空指针,则退出程序
sequence->length = 0; //初始表长为0,此时顺序表为空表,其中没有被插入任何元素
sequence->MaxSize = MAXSIZE; //初始最大表长为常量MAXSIZE
}
//初始化顺序表操作
②求表长
顺序表的求表长操作并没有任何难度,直接访问变量SqList.length即可,因此没有必要特意再编写一个求表长的函数。
与之相比,链表的求表长操作相对复杂一点,顺着指针域中的指针一个个结点地移动,每移动一个结点,计数器变量都会加1,直到访问到空指针域,此时便将计数器的值作为求表长函数的返回值,详情见下一篇关于链表的笔记。
printf("Now, the length of the sequence list SqList is %d\n", SqList.length);
//输出此刻的表长
③插入操作
由于顺序表在内存中的存储结构为连续存储,所以每插入一个元素,都必须将其后续的元素从后到前依次向后移动一个存储单元,如下图所示:
C语言代码实现如下:
#include<stdio.h>
#include<stdbool.h>
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
bool InsertList(SeqList* sequence, double Add, int Loc) {
if (Loc < 1 || Loc > sequence->length + 1) {
printf("Please input valid location!\n");
return false;
} //判断位序参数Loc的有效性,若不在有效范围内,则返回错误
//Loc即插入表中的元素的位序,有效值为1~length+1
if (sequence->length >= sequence->MaxSize) {
printf("The element Add cannot be inserted into a full list!\n");
return false;
} //若表长已达到最大表长,则无法再插入新的元素
for (int count = sequence->length; count >= Loc; count--) {
sequence->Data[count] = sequence->Data[count - 1];
} //从位序为length的元素到位序为Loc的元素,依次后移一个存储单元
//注意:哪怕循环体中只有一条语句,为了代码的可读性也一定要打上大括号!教材中的辣鸡格式不可取
sequence->Data[Loc - 1] = Add; //后续元素移动完成后,将元素Add插入位序为Loc的位置
//注意:位序是以1为起点的,而数组下标是以0为起点的,对应关系为:数组下标=位序-1
sequence->length++; //表长加1
return true;
}
//插入操作,将元素Add插入至位序为Loc的位置
上述程序中,若表长达到最大表长,则无法再插入新的元素,与静态分配的顺序表并无差别,没有体现出动态分配顺序表的优势。当表长达到最大表长时,可选择开辟一片新的存储空间,扩充表长,并将原存储空间中的数据复制过去,改进后的程序如下:
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#define MAXSIZE 100
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
bool InsertList(SeqList* sequence, double Add, int Loc) {
if (Loc < 1 || Loc > sequence->length + 1) {
printf("Please input valid location!\n");
return false;
} //判断位序参数Loc的有效性,若不在有效范围内,则返回错误
//Loc即插入表中的元素的位序,有效值为1~length+1
if (sequence->length >= sequence->MaxSize) {
double* Temp = sequence->Data;
//定义一个指向顺序表原存储空间的临时指针变量
sequence->Data = (double*)malloc(sizeof(double) * (sequence->MaxSize + MAXSIZE));
//结构体中的指针指向新开辟的一片更大的存储空间(每次扩充MAXSIZE个存储单元)
if (!sequence->Data) {
printf("Allocation Failure!\n");
exit(EXIT_FAILURE);
} //若动态分配连续存储空间失败,即返回给sequence->Data的是一个空指针,则退出程序
for (int loc = 1; loc <= sequence->length; loc++) {
sequence->Data[loc - 1] = Temp[loc - 1];
} //将原表中的内容复制到新表
free(Temp); //释放原表存储空间
sequence->MaxSize += MAXSIZE; //最大表长加MAXSIZE
printf("The length has been extended.\n"); //输出表示最大表长已扩充
}
//若表长已达到最大表长,则开辟一片新的存储空间,扩充表长
for (int count = sequence->length; count >= Loc; count--) {
sequence->Data[count] = sequence->Data[count - 1];
} //从位序为length的元素到位序为Loc的元素,依次后移一个存储单元
sequence->Data[Loc - 1] = Add; //后续元素移动完成后,将元素Add插入位序为Loc的位置
//注意:位序是以1为起点的,而数组下标是以0为起点的,对应关系为:数组下标=位序-1
sequence->length++; //表长加1
return true;
}
//插入操作,将元素Add插入至位序为Loc的位置
④删除操作
从顺序表中删除一个元素,则从被删除的元素的后一个元素起,后续每个元素都向前移动一个存储单元,如下图所示:
代码实现如下:
#include<stdio.h>
#include<stdbool.h>
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
bool DeleteList(SeqList* sequence, int Loc, double* deleted) {
if (Loc < 1 || Loc > sequence->length) {
printf("Input valid location!\n");
return false;
} //判断Loc是否为有效位序,若不在有效范围1~length内,则退出程序,返回错误
*deleted = sequence->Data[Loc - 1]; //用指针变量deleted接收被删除的元素的值Data[Loc-1]
for (int count = Loc; count <= sequence->length - 1; count++) {
sequence->Data[count - 1] = sequence->Data[count];
} //自被删除的元素的下一项起,从前到后依次将各元素向前移动一个存储单元
sequence->length--; //表长减1
return true;
}
//删除操作,将位序为Loc处的元素从顺序表中移除
⑤按位查找操作
给定位序Loc,查找表中第Loc个元素。
#include<stdio.h>
#include<stdbool.h>
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
bool GetElem(SeqList* sequence, int Loc, double* Elem) {
if (Loc < 1 || Loc > sequence->length) {
printf("Input valid location!\n");
return false;
} //判断Loc是否为有效位序,若不在有效范围1~length内,则退出程序,返回错误
*Elem = sequence->Data[Loc - 1]; //用指针变量Elem接收所查找到的值Data[Loc-1]
printf("The value in the location %d is %.3lf.\n", Loc, *Elem); //屏幕输出查找结果
return true;
}
//按位查找操作,查找表中第Loc位元素,用Elem接收查找结果
⑥按值查找操作
给定目标值value,查找value在表中第一次出现时的位序。
#include<stdio.h>
#include<stdbool.h>
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
bool LocateElem(SeqList* sequence, double value, int* result) {
*result = 0; //*result初始赋值0,若查找失败,则查找结果*result依旧为无效位序0
for (int counter = 1; counter <= sequence->length; counter++) {
if (sequence->Data[counter-1] == value) {
*result = counter; //返回有效查找结果(目标值第一次出现的位序)
printf("Array(%d) = %.3lf\n", *result, value); //输出成功查找结果
return true; //查找成功,返回true
}
}
printf("Selection Failure!\n"); //输出失败查找结果
return false; //查找失败,返回false
}
//按值查找操作,查找目标值value在表中第一次出现的位序
⑦输出操作
按照前后顺序,输出线性表SqList,代码实现如下:
#include<stdio.h>
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
void PrintList(SeqList* sequence) {
if (sequence->length == 0) {
printf("There's no element in the list.\n");
} //若为空表,则输出空表提示
else {
printf("The elements in the list: ");
for (int count = 1; count <= sequence->length; count++) {
printf("%.3lf", sequence->Data[count - 1]); //输出保留三位小数
if (count < sequence->length) {
printf(", "); //每两个输出值之间打印一个逗号和空格
}
}
printf("\n"); //输出完毕后换行
} //若不为空表,则正常输出
} //按前后顺序屏幕输出表中各元素
⑧判空操作
算法非常简单,表长为0则空,反正则不为空。
#include<stdbool.h>
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
bool Empty(SeqList* sequence) {
if (sequence->length) {
return false;
} //表不为空,返回false
else {
return true;
} //表为空,返回true
} //判断线性表是否为空,若为空则返回true,否则返回false
⑨销毁操作
释放线性表所占用的内存空间。
#include<stdlib.h>
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
void DestroyList(SeqList* sequence) {
free(sequence->Data); //释放动态分配的一块连续的内存空间
sequence->Data = NULL; //将指针sequence->Data置空
sequence->length = 0; //将线性表长度置0
sequence->MaxSize = 0; //最大长度置0
} //销毁操作,释放线性表所占用的内存空间
四 自选操作游戏
现将以上的顺序表操作合起来,在main函数中编写一个对表进行各种基本操作的程序,操作类型,以及所传参数,都由用户手动键盘输入。
到头来,只做了这么一个无聊透顶的小游戏,实在闲得过于发慌就运行一下玩玩吧哈哈。
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#define MAXSIZE 100
typedef struct {
double* Data; //指向顺序表首地址的指针
int length, MaxSize; //记录顺序表当前表长和最大表长
} SeqList;
//包含了顺序表首地址和表长信息的结构体
void InitList(SeqList* sequence); //初始化顺序表操作
bool InsertList(SeqList* sequence, double Add, int Loc);
//插入操作,将元素Add插入至位序为Loc的位置
bool DeleteList(SeqList* sequence, int Loc, double* deleted);
//删除操作,将位序为Loc处的元素从顺序表中移除
bool GetElem(SeqList* sequence, int Loc, double* Elem);
//按位查找操作,查找表中第Loc位元素,用Elem接收查找结果
bool LocateElem(SeqList* sequence, double value, int* result);
//按值查找操作,查找目标值value再表中第一次出现的位序
void PrintList(SeqList* sequence); //按前后顺序屏幕输出表中各元素
bool Empty(SeqList* sequence);
//判断线性表是否为空,若为空则返回true,否则返回false
void DestroyList(SeqList* sequence);
//销毁操作,释放线性表所占用的内存空间
void ClearBuffer(void); //清除缓冲区操作
int main(void) {
SeqList SqList; //定义结构体变量SqList
char ch; //用于识别对线性表的操作类型的字符变量
int loc, selectResult; //用于传参的位序,及按值查找操作所返回的位序
double element, elementDeleted, elementDetected;
//用于传参的元素值,删除操作返回的元素值,及按位查找操作所返回的元素值
InitList(&SqList);
//初始化顺序表,由指针SqList.Data指向表的首地址
printf("There are 8 operations for the list:\n"
"s: InitList\n"
"i: InsertList\n"
"d: DeleteList\n"
"l: GetElem\n"
"v: LocateElem\n"
"p: PrintList\n"
"e: Empty\n"
"h: DestroyList\n"
"Please input the codes to choose different operations.\n");
//操作指南
while (1) {
printf("Input an operation code: ");
ch = getchar(); //输入操作所对应的字符
if (ch == 's') {
getchar(); //吞掉换行符
free(SqList.Data); //初始化前,先释放原存储空间,以防内存泄露
InitList(&SqList);
printf("The sequence list has been successfully initialized.\n");
//输出提示初始化成功
}
//初始化线性表
else if (ch == 'i') {
getchar(); //吞掉换行符
printf("Input the inserted element: ");
while (scanf("%lf", &element) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
printf("Input the location: ");
while (scanf("%d", &loc) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
getchar(); //吞掉换行符
InsertList(&SqList, element, loc);
PrintList(&SqList);
}
//插入操作
else if(ch == 'd'){
getchar(); //吞掉换行符
printf("Input the location of the deleted element: ");
while (scanf("%d", &loc) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
getchar(); //吞掉换行符
if (DeleteList(&SqList, loc, &elementDeleted)) {
printf("%.3lf, the No.%d element of the list, has been deleted.\n", elementDeleted, loc);
}
PrintList(&SqList);
}
//删除操作
else if (ch == 'l') {
getchar(); //吞掉换行符
printf("Input the location which will be detected: ");
while (scanf("%d", &loc) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
getchar(); //吞掉换行符
GetElem(&SqList, loc, &elementDetected);
}
//按位查找
else if (ch == 'v') {
getchar(); //吞掉换行符
printf("Input the value which will be selected: ");
while (scanf("%lf", &element) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
getchar(); //吞掉换行符
LocateElem(&SqList, element, &selectResult);
}
//按值查找
else if (ch == 'p') {
getchar(); //吞掉换行符
PrintList(&SqList);
}
//输出线性表
else if (ch == 'e') {
getchar(); //吞掉换行符
if (Empty(&SqList)) {
printf("The list is null!\n");
}
else {
printf("The list is not null.\n");
}
}
//线性表判空
else if (ch == 'h') {
getchar(); //吞掉换行符
DestroyList(&SqList);
printf("The list has been destroyed!\n");
}
//销毁线性表
else if (ch == '\n') {
}
//若从输入缓冲区读取到'\n',则什么也不执行
else {
printf("Input valid code!\n");
ClearBuffer(); //清除stdin输入缓冲区
}
//排除非法输入的字符
printf("\n");
//为了在格式上区分每一步操作,每一步循环的末尾都要换行
}
return 0;
}
void InitList(SeqList* sequence) {
sequence->Data = (double*)malloc(sizeof(double) * MAXSIZE);
//动态分配顺序表的存储空间,初始最大表长设为MAXSIZE
if (!sequence->Data) {
printf("Allocation Failure!\n");
exit(EXIT_FAILURE);
} //若动态分配连续存储空间失败,即返回给sequence->Data的是一个空指针,则退出程序
sequence->length = 0; //初始表长为0,此时顺序表为空表,其中没有被插入任何元素
sequence->MaxSize = MAXSIZE; //初始最大表长为常量MAXSIZE
}
//初始化顺序表操作
bool InsertList(SeqList* sequence, double Add, int Loc) {
if (Loc < 1 || Loc > sequence->length + 1) {
printf("Please input valid location!\n");
return false;
} //判断位序参数Loc的有效性,若不在有效范围内,则返回错误
//Loc即插入表中的元素的位序,有效值为1~length+1
if (sequence->length >= sequence->MaxSize) {
double* Temp = sequence->Data;
//定义一个指向顺序表原存储空间的临时指针变量
sequence->Data = (double*)malloc(sizeof(double) * (sequence->MaxSize + MAXSIZE));
//结构体中的指针指向新开辟的一片更大的存储空间(每次扩充MAXSIZE个存储单元)
if (!sequence->Data) {
printf("Allocation Failure!\n");
exit(EXIT_FAILURE);
} //若动态分配连续存储空间失败,即返回给sequence->Data的是一个空指针,则退出程序
for (int loc = 1; loc <= sequence->length; loc++) {
sequence->Data[loc - 1] = Temp[loc - 1];
} //将原表中的内容复制到新表
free(Temp); //释放原表存储空间
sequence->MaxSize += MAXSIZE; //最大表长加MAXSIZE
printf("The length has been extended.\n"); //输出表示最大表长已扩充
}
//若表长已达到最大表长,则开辟一片新的存储空间,扩充表长
for (int count = sequence->length; count >= Loc; count--) {
sequence->Data[count] = sequence->Data[count - 1];
} //从位序为length的元素到位序为Loc的元素,依次后移一个存储单元
sequence->Data[Loc - 1] = Add; //后续元素移动完成后,将元素Add插入位序为Loc的位置
//注意:位序是以1为起点的,而数组下标是以0为起点的,对应关系为:数组下标=位序-1
sequence->length++; //表长加1
return true;
}
//插入操作,将元素Add插入至位序为Loc的位置
bool DeleteList(SeqList* sequence, int Loc, double* deleted) {
if (Loc < 1 || Loc > sequence->length) {
printf("Input valid location!\n");
return false;
} //判断Loc是否为有效位序,若不在有效范围1~length内,则退出程序,返回错误
*deleted = sequence->Data[Loc - 1]; //用指针变量deleted接收被删除的元素的值Data[Loc-1]
for (int count = Loc; count <= sequence->length - 1; count++) {
sequence->Data[count - 1] = sequence->Data[count];
} //自被删除的元素的下一项起,从前到后依次将各元素向前移动一个存储单元
sequence->length--; //表长减1
return true;
}
//删除操作,将位序为Loc处的元素从顺序表中移除
bool GetElem(SeqList* sequence, int Loc, double* Elem) {
if (Loc < 1 || Loc > sequence->length) {
printf("Input valid location!\n");
return false;
} //判断Loc是否为有效位序,若不在有效范围1~length内,则退出程序,返回错误
*Elem = sequence->Data[Loc - 1]; //用指针变量Elem接收所查找到的值Data[Loc-1]
printf("The value in the location %d is %.3lf.\n", Loc, *Elem); //屏幕输出查找结果
return true;
}
//按位查找操作,查找表中第Loc位元素,用Elem接收查找结果
bool LocateElem(SeqList* sequence, double value, int* result) {
*result = 0; //*result初始赋值0,若查找失败,则查找结果*result依旧为无效位序0
for (int counter = 1; counter <= sequence->length; counter++) {
if (sequence->Data[counter-1] == value) {
*result = counter; //返回有效查找结果(目标值第一次出现的位序)
printf("Array(%d) = %.3lf\n", *result, value); //输出成功查找结果
return true; //查找成功,返回true
}
}
printf("Selection Failure!\n"); //输出失败查找结果
return false; //查找失败,返回false
}
//按值查找操作,查找目标值value在表中第一次出现的位序
void PrintList(SeqList* sequence) {
if (sequence->length == 0) {
printf("There's no element in the list.\n");
} //若为空表,则输出空表提示
else {
printf("The elements in the list: ");
for (int count = 1; count <= sequence->length; count++) {
printf("%.3lf", sequence->Data[count - 1]); //输出保留三位小数
if (count < sequence->length) {
printf(", "); //每两个输出值之间打印一个逗号和空格
}
}
printf("\n"); //输出完毕后换行
} //若不为空表,则正常输出
} //按前后顺序屏幕输出表中各元素
bool Empty(SeqList* sequence) {
if (sequence->length) {
return false;
} //表不为空,返回false
else {
return true;
} //表为空,返回true
} //判断线性表是否为空,若为空则返回true,否则返回false
void DestroyList(SeqList* sequence) {
free(sequence->Data); //释放动态分配的一块连续的内存空间
sequence->Data = NULL; //将指针sequence->Data置空
sequence->length = 0; //将线性表长度置0
sequence->MaxSize = 0; //最大长度置0
} //销毁操作,释放线性表所占用的内存空间
void ClearBuffer(void) {
char ch;
while ((ch=getchar())!='\n' && ch != EOF) {
}
} //清除缓冲区操作