1.顺序表定义及特点
线性表的顺序存储又称顺序表。
是用一组地址连续的存储单元依次存储 线性表中的数据元素,从而使得逻辑上相邻 的两个元素在物理位置上也相邻。
顺序表特点是元素的逻辑顺序与其物理顺序相同。
第 1 个元素存储在线性表的起始位置,第 i 个元素的存储位置后紧接着存储的是第 i+1 个元素,称 i 为元素 在线性表中的位序。注意,线性表中元素位序从 1 开始,而数组中元素下标从 0 开始。
线性表中任一元素都可以随机存取,原因如下(理解的可以跳过):
假设线性表 L 存储的起始位置是 LOC(A),sizeof(ElemType)是每个数据元素所占用的存储空间的大小,因为线性表的定义中指明所有数据元素类型都相同,所以表中每个元素对应的sizeof(ElemType)都相同。对于每一个数据元素,它的存储位置和线性表的起始位置之间的差值就是sizeof(ElemType)的倍数,并且这个倍数和这个元素的位序有关,比如表中第三个数据元素的位置和这个起始位置之间就差了两个sizeof(ElemType)的大小(存储了第一个和第二个数据元素),所以想要找到任意一个位序的元素的位置都可以根据这个关系直接找到,而不用从第一个开始一个一个往后找。
假设线性表的元素类型为ElemType(其实就相当于 int,double 等类型,就只是换了个抽象的名),则线性表的顺序存储类型描述为:
#define MaxSize 50 // 宏定义,这里用来定义表的最大长度
typedef struct{ // 定义一个新的结构体类型
ElemType data[MaxSize]; # 定义一个长度为MaxSize,类型为ElemType的数组,这里是静态分配
int length; // 顺序表当前长度
}SqList; // 类型别名
这里首先要注意 typedef :
基本用法:
typedef 已有类型 新类型名;
例如:
typedef int Integer; // 给已有类型 int 起一个新类型名Integer
所以上面线性表的顺序存储类型的代码中,struct { ... }定义了一个匿名结构体类型,然后用 typedef 给这个结构体类型起了一个别名 SqList,使用的时候就像 int a, b; 一样,可以直接SqList L1, L2。
int a,b;
SqList L1,L2;
定义结构体的时候还有另一种写法,不用 typedef 起别名,而是直接写上结构体标签,但这种方法定义的结构体在使用的时候必须写 structSqList L; 而不能直接用 SqList L; 。
struct SqList{ /* 没有typedef */
ElemType data[MaxSize]; /* 定义一个长度为MaxSize,类型为ElemType的数组,这里是静态分配 */
int length; /* 顺序表当前长度 */
};
/* 使用时必须写struct! */
struct SqList L;
另外一个要注意的地方是这里的数组是用"类型 数组名[大小]"来静态分配 的,静态指的是数组的大小和空间已经固定,如果数组满了再往里加新数据就会溢出;对应的动态分配是指存储数组的空间在程序运行时是动态分配的,如果原来的空间占满了就开辟一个更大的来替换原来的。动态分配语句如下:
/* C */
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);
/* C++ */
L.data = new ElemType[InitSize];
最后再总结一下顺序表的特点:
1.元素的逻辑顺序与其物理顺序相同。
2.随机访问,即通过首地址和元素序号可在时间O(1)内找到指定元素。
3.存储密度高,每个结点只存储数据元素。
4.插入和删除操作需要移动大量元素(因为所有数据都是按顺序挨着的,无论是插入还是删除后面的元素一定会依次移动位置)。
2.顺序表基本操作 代码及时间复杂度
顺序表是线性表的顺序存储,所以也有线性表的几个基本操作(可以参考https://blog.csdn.net/XinxingZh/article/details/156107626?fromshare=blogdetail&sharetype=blogdetail&sharerId=156107626&sharerefer=PC&sharesource=XinxingZh&sharefrom=from_link),这里只讨论初始化、插入、删除、按值查找。
(1)初始化
初始化只有一个将表长置为 0 的操作,需要的话也可以加上把表中元素全都置为 0 或是其他值的操作。需要注意的地方就是这里的参数,有一些指针的内容需要理解。完整的代码放在最后。
#include <stdio.h>
#define ElemType int /* 定义数据类型 */
#define MaxSize 50 /* 定义最大表长 */
typedef struct{
ElemType data[MaxSize];
int length; /* 当前表长 */
}SqList;
/* 初始化 */
/* C语言中没有引用(&)概念,这里只能用指针
参数类型实际上是 SqList * ,一个指向 SqList 类型的指针
参数类型并不是 SqList
*/
void InitList(SqList *L) { //可以写成 SqList* L
L->length = 0; //等价于(*L).length,结构体用.指针用->
}
/* *********************** */
//调用 InitList(&L);
(2)求表长
求表长的操作也很简单,直接返回结构体的 length 的值即可。
/*求表长*/
/* 写法一:参数是结构体 */
int ListLength(SqList L) {
return L.length;
}
// 调用:int len = ListLength(L);
/* 写法二:参数是指针 */
/* 这种方式内存效率更高,为了考试或日常学习的话两种都行 */
int ListLengthPtr(const SqList *L) { //const 是为了保证不会修改原结构体
return L->length;
}
// 调用:int len = ListLengthPtr(&L);
(3)插入
思路 :过程大体分为三步,先找到要插入的位置(注意判断位置是否合法)--->后面的元素后移(为了把这个位置空出来)--->把数插入指定位置。
有几个细节问题:
-
函数返回值是什么类型:能插入则在插入完成后返回 true ,不能插入的情况之间返回 false。用bool类型需要加头文件,也可以用 int 类型的 1 0表示true 和 false。
-
有哪几种情况不能插入:要插入的位序小于 1 或者超过表长+1 (插入位置是表中元素的位序,从 1 开始,不是数组下标);当前存储空间已满。
-
成功插入后要将表长 +1 。
#include <stdbool.h>
bool ListInsert(SqList *L, int index, ElemType e){
if(index < 1 || index > L->length+1) //插入位序小于1或大于表长+1
return false;
if(L->length >= MaxSize) //存储空间已满
return false;
for(int i = L->length;i>=index;i--) //index及之后的数据后移
L->data[i] = L->data[i-1];
L->data[index-1] = e; //在index位置插入元素
L->length++; //别忘了修改当前表长
return true;
}
时间复杂度分析:
最好情况:在表尾插入,元素不需要后移(for循环里的语句不执行),时间复杂度为O(1)。
最坏情况:在表头插入,要移动表长个元素,时间复杂度为O(n)。
平均情况:公式就不写了,可以简单理解为平均情况就是在中间插入,要移动一半的元素,平均次数就是n/2,时间复杂度还是O(n)。
(4)删除
删除操作指的是在当前表的合法位置里删除某个位置的元素,这里用一个变量返回。和插入操作类似的是都要判断位置是否合法、删除位置之后的元素移动位置、删除之后要改变表长。时间复杂度和插入操作一致。
bool ListDelete(SqList *L, int index, ElemType *e){
if(index < 1 || index > L->length) //判断位置是否合法
return false;
*e = L->data[index-1]; //获取被删除的元素值
for(int i = index-1; i < L->length; i++)
L->data[i]=L->data[i+1]; //后续元素前移
L->length--; //表长-1
return true;
}
(5)按值查找
查找顺序表中第一个值等于e的元素,并返回其位序。思路显然就是从第一个开始一个一个对比。
/*按值查找*/
int LocateElem(SqList L, ElemType e){
for(int i=0; i<L.length; i++){
if(L.data[i]==e)
return i+1;
}
return 0; //这里返回的是位序,所以0也表示没找到,也可以改成-1
}
时间复杂度的最好、最坏、平均情况和插入、删除类似,因为这三个操作的最好情况都是开头就结束,最差情况都是一直找到表尾,平均情况就是要一直到中间位置。
顺序表可以直接返回某个位置的值,所以就没有按位查找算法了。
(6)输出表
为了方便观察运行过程写一个按顺序输出表中元素值的算法。
/*输出*/
void PrintList(SqList L) {
if (L.length == 0) {
printf("顺序表为空!\n");
return;
}
printf("顺序表内容:");
for (int i = 0; i < L.length; i++) {
printf("%d ", L.data[i]); //占位符可根据实际进行修改
}
printf("\n");
}
(7)完整可运行代码
内容可以根据实际自己调整练习,保存为.c文件即可。
#include <stdio.h>
#include <stdbool.h>
#define ElemType int /* 定义数据类型 */
#define MaxSize 50 /* 定义最大表长 */
typedef struct{
ElemType data[MaxSize];
int length; /* 当前表长 */
}SqList;
/* 初始化 */
/* C语言中没有引用(&)概念,这里只能用指针
参数类型实际上是 SqList * ,一个指向 SqList 类型的指针
参数类型并不是 SqList
*/
void InitList(SqList *L) { //可以写成 SqList* L
L->length = 0; //等价于(*L).length,结构体用.指针用->
}
/* *********************** */
/*求表长*/
/* 写法一:参数是结构体 */
int ListLength(SqList L) {
return L.length;
}
// 调用:int len = ListLength(L);
/* 写法二:参数是指针 */
/* 这种方式内存效率更高,为了考试或日常学习的话两种都行 */
int ListLengthPtr(const SqList *L) { //const 是为了保证不会修改原结构体
return L->length;
}
// 调用:int len = ListLengthPtr(&L);
/*插入*/
bool ListInsert(SqList *L, int index, ElemType e){
if(index < 1 || index > L->length+1) //插入位序小于1或大于表长+1
return false;
if(L->length >= MaxSize) //存储空间已满
return false;
for(int i = L->length;i>=index;i--) //index及之后的数据后移
L->data[i] = L->data[i-1];
L->data[index-1] = e; //在index位置插入元素
L->length++; //别忘了修改当前表长
return true;
}
/*删除*/
bool ListDelete(SqList *L, int index, ElemType *e){
if(index < 1 || index > L->length) //判断位置是否合法
return false;
*e = L->data[index-1]; //获取被删除的元素值
for(int i = index-1; i < L->length; i++)
L->data[i]=L->data[i+1]; //后续元素前移
L->length--; //表长-1
return true;
}
/*按值查找*/
int LocateElem(SqList L, ElemType e){
for(int i=0; i<L.length; i++){
if(L.data[i]==e)
return i+1;
}
return 0; //这里返回的是位序,所以0也表示没找到,也可以改成-1
}
/*输出*/
void PrintList(SqList L) {
if (L.length == 0) {
printf("顺序表为空!\n");
return;
}
printf("顺序表内容:");
for (int i = 0; i < L.length; i++) {
printf("%d ", L.data[i]); //占位符可根据实际进行修改
}
printf("\n");
}
int main(void){
SqList L;
/*初始化*/
InitList(&L);
int len = ListLength(L);
printf("当前表长为%d\n", len);
/*插入*/
ListInsert(&L, 1, 3);
len = ListLength(L);
printf("当前表长为%d\n", len);
ListInsert(&L, 1, 5);
ListInsert(&L, 1, 7);
len = ListLength(L);
printf("当前表长为%d\n", len);
PrintList(L);
/*删除*/
ElemType e;
ListDelete(&L, 1, &e);
len = ListLength(L);
printf("当前表长为%d\n", len);
printf("删除的元素为%d\n", e);
PrintList(L);
/*按值查找*/
e=3;
int index = LocateElem(L, e);
printf("%d在第%d个位置", e, index);
return 0;
}