前言
我们都知道,数据结构中逻辑结构可以划分为线性结构(线性表)与非线性结构两大类。
而存储结构指的是数据元素在计算机中的存储及其逻辑关系的表现,也就是在计算机当中对逻辑结构的表示。
线性表的存储结构主要有顺序结构和链式结构两种实现形式。本文主要探讨基于线性表的顺序结构也就是顺序表的四种基本操作:初始化、插入、删除、查找。
这些知识点既可能出现在选择题的考察当中又可以出现在编程大题当中,但是考察的侧重点不同,选择题重点关注操作的时间复杂度以及特性(特别是不同结构的顺序表,在实现某种操作时候的效率高低),编程大题只是关注代码实现能力。
基本知识分析
顺序存储 :
把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。(为了便于理解,大家可以近似得把这一段空间理解成一个C语言数组)
由于元素顺序排列,顺序存储具有以下性质和特征:
• 线性表的逻辑顺序与物理顺序一致;
• 数据元素之间的关系是以元素在计算机内“物理位置相邻”来体现
a1到an存放情况如图所示,设每个元素占l个单元长度。(ps:计算机中数组下标实际从0开始)
ai的地址:Loc(ai)=Loc(a1)+(i-1)×1
因此顺序表的结构体当中应当包含 一块连续的地址空间、当前存放元素的长度、当前分配的存储容量。由此写出结构体如下:
typedef struct{
ElemType *elem;//存储空间基址
int length;//当前长度
int listsize;//当前分配的存储容量
}SqList;
在了解顺序表静态构造的基础上,我们可以在这个基础上构建它的几个基本操作了。
1初始化
输入:MAXSIZE,表示要申请MAXSIZE个元素大小的地址单元。
关键代码:
L->elem=(ElemType *)malloc(MAX_SIZE*sizeof( ElemType));//分配空间
L->length=0;//空表长度为0
L->listsize=MAX_SIZE;//初始存储容量
插入
设n个元素存放在elem[0...length-1]内。
输入:i,e,表示要在下标为i处插入值为e的元素。
分析插入过程导致的结果:元素数量从length变成了length+1,如果只是简单地将下标为i处的元素替换成e,会导致原先的元素丢失。因此插入操作可以表述为:(1)将该元素以及该元素之后的所有元素都向后移动一个位。(2)元素大小加1。(3)再将e写入到下标为i处。比如对于[1,2]这个数组来说,将0插入到第一位,事实上是先使得第一位以及第一位之后的所有元素后移一位,数组变成[1,1,2],然后将0写入第一位,数组变成[0,1,2]
关键代码:
//elem[i...length-1]向后移动for(j = length-1; j >= i; j--){
L->elem[j+1] = L->elem[j]
}//元素大小加1
length++;//e写入下标为i处
L->elem[i]=e;
删除
设n个元素存放在elem[0...length-1]内。
输入:i,表示要删除下标为i处的元素。
分析删除过程导致的结果:元素数量从length变成了length-1,如果只是简单地将下标为i处的元素被删除,会导致此处出现一个空白单元。因此删除操作可以表述为:(1)将该元素之后的所有元素都向前移动一个位。(2)元素大小减1。将i+1处的元素移动到i处时完成了覆盖,事实上等同于删除了i处的元素。比如对于[0,1,2]这个数组来说,将第二个元素移动到第一个,第三个元素移动到第二个也就是数组变成了[1,2,2],但是此时length=2说明数组长度为2,取前2个元素,事实上完成了对0的删除。
关键代码:
//elem[i+1...length-1]向前移动for(j = length-1; j > i; j--){
L->elem[j-1] = L->elem[j]
}//元素大小减1
length--;
查找
设n个元素存放在elem[0...length-1]内。
输入:e,表示查找值为e的元素。
输出:i,表示值为e的元素位于下标为i处,若查找不成功,i=-1(或者自己定义其他不属于0...length-1的值)
分析查找过程:其实是从头到尾遍历每一个元素,比较当前元素是否等于查找值e,若等于则返回下标i,当遍历完毕n个元素还是没有返回值,说明表中不存在要查询的元素。
关键代码:
//遍历比较每一个元素for(i=0; i < length; i++){if(EQ(L->elem[i], e)){ return i;}
}//遍历结束没有返回值,说明不存在return -1;
选择题角度
重点考察 时间复杂度、特性、以及执行相关操作的效率。
根据前面的分析以及关键代码部分可以观察到:
1初始化
关键代码只涉及到一次堆分配malloc以及修改listsize和length,频度为3,时间复杂度为O(1)。
2插入
关键代码分为三步:(1)修改length,频度为1,时间复杂度为O(1);(2)elem[i...n-1]向后移动,移动n-i次,频度为n-i,时间复杂度为O(n);(3)向下标为i处写入元素e,频度为1,时间复杂度为O(1)。综上总的时间复杂度为O(n)。
3删除
关键代码分为两步:(1)修改length,频度为1,时间复杂度为O(1);(2)elem[i+1...n-1]向前移动,移动n-i-1次,频度为n-i-1,时间复杂度为O(n)。综上总的时间复杂度为O(n)。
4查找
关键代码:遍历整个序列比较是否有元素的值为e,遍历结束时查找不成功则返回-1。显然若第一个元素就是要找的元素时,只比较一次,若最后一个元素是要找的元素则比较n次,若不存在该元素同样是比较n次,比较次数的取值范围为1到n,若每个元素出现频率相等,查找成功的情况下平均比较次数为(n+1)/2次,时间复杂度为O(n)。
综上,在顺序表中,访问下标为i的元素可以通过 随机访问 ,如elem[i]获取,时间复杂度为O(1),但是对于插入和删除这样的动态操作时间复杂度都为O(n),顺序查找时间复杂度也为O(n)。
编程角度
一个完整的操作函数,是由 健壮性保证 以及 关键代码 两部分组成的。
1初始化
malloc可能会有分配失败的可能,因此要对此进行判断。
bool InitList(SqList *L){
L->elem=(ElemType *)malloc(MAX_SIZE*sizeof( ElemType));//分配空间if(!L->elem){ return false;}//基址指针为空时分配失败
L->length=0;//空表长度为0
L->listsize=MAX_SIZE;//初始存储容量return true;
}
插入
• 对于elem[0...length-1]来说合法的插入范围应该是0~length,要对输入的i进行判断。
• 插入会使得表长加1,可能会发生上溢,也就是分配空间不够,所以要对此进行判断。
bool ListInsert(SqList *L, int i, ElemType e){int j;if(i < 0 || i > L->length) { return false;}//i输入是否合法if(L->length >= L->listsize){
newbase = (ElemType *)realloc(L->elem, (L->listsize + INCREMENT)*sizeof( ElemType));//分配空间if(!newbase){return false;}//分配失败
L->elem = newbase;
L->listsize += INCREMENT;
}//是否上溢//elem[i...n-1]向后移动for(j = L->length-1; j >= i; j--){
L->elem[j+1] = L->elem[j]
}//元素大小加1
L->length++;//e写入下标为i处
L->elem[i]=e;return true;
}
删除
• 对于elem[0...length-1]来说合法的删除范围应该是0~length-1,要对输入的i进行判断。
bool ListDelete(SqList *L, int i, ElemType &e){if(i < 0 || i > L->length - 1){ retrun false;}//i输入是否合法
e = *(&L->elem[i]);//elem[i+1...length-1]向前移动for(j = L->length-1; j > i; j--){
L->elem[j-1] = L->elem[j]
}//元素大小减1
L->length--;return true;
}
查找
int Locate(SqList* L, ElemType e){int i;//遍历比较每一个元素for(i=0; i < L.length; i++){if(EQ(L->elem[i], e)){ return i;}
}//遍历结束没有返回值,说明不存在return -1;
}
以上就是学长给大家归纳的关于线性表的相关基本操作了。
这里大牛学长帮大家最后总结一下。
⭕ 对于增、删、查、改几个基本操作,由于顺序表元素存在于一片连续空间。每一次做遍历相关操作的时候,都需要用一个全局的for循环去近似遍历整个表,因此这几个操作的时间复杂度都是O(n),即线性的。
⭕ 对于增、删操作,一定要做好合法性判断,在编程大题中,合法性判断是判卷老师对于学生编程素质的重点考察点,你不能说老师一定会关注合法性判断,但是写上合法性判断相关的代码一定会为你加上一点“印象分”!
今天是2020年8月18日
距离2021考研还有 122 天
全力以赴,才有资格说尽力。
大牛学长一直在~