1.文章内容摘抄自严蔚敏、吴伟民《数据结构(C语言版)》,供学习参考使用。
2.数据结构考研完整C语言代码可参照这位博主的文章,超全面:https://blog.youkuaiyun.com/lady_killer9/article/details/82695895
3.顺序表的C语言实现也可参考,和严蔚敏老师书上的算法是很贴近的:https://www.cnblogs.com/summer-4/p/15366530.html
一、线性表的类型定义
线性结构的特点:在数据元素的非空间有限集中,(1)存在唯一的一个被称作为“第一个”的数据元素;(2)存在唯一的一个被称作“最后一个”的数据元素;(3)除第一个之外,集合中的每个数据元素都只有一个前驱;(4)除最后一个之外,集合中的每个数据元素都只有一个后继。
线性表 是最常用且最简单的一种数据结构。简言之,一个线性表是n个数据元素的有限序列。
在复杂的线性表中,一个数据元素可以由若干个 数据项 组成。在这种情况下,常把数据元素称为 记录 ,含有大量记录的线性表又称 文件 。
线性表中的数据元素可以是各种各样的,但同一线性表中的元素必定具有相同特性,即属于同一数据对象,相邻数据元素之间存在着序偶关系。
线性表中元素个数n定义为线性表的长度,n=0时称为 空表。
二、线性表的顺序表示的表示与实现
2.1-顺序表的定义
线性表的顺序表示指的是用一组 地址连续的存储单元 依次存储线性表的数据元素,这种存储结构的线性表也被称为 顺序表,其 特点是以元素在计算机内“物理位置相邻” 来表示线性表中数据元素之间的逻辑关系。
若线性表中每个元素需占用L个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储位置,则线性表中第i+1个数据元素的存储位置LOC(ai+1)为:
LOC(a i+1) = LOC(a 1) + (i-1)×L
其中LOC(a1)是线性表的第一个数据元素a1的存储位置,通常被称为线性表的起始位置或基地址。只要确定了存储线性表的起始位置,线性表中任一数据元素都可以实现 随机存取。因此,线性表的顺序存储结构是一种随机存取的存储结构。
2.2-顺序表的动态分配与初始化
1.动态分配:在C语言中可动态分配一维数组,如下:
//———————————————— 线性表的动态分配顺序存储结构 ————————————————————
#define LIST_INIT_SIZE 100 // 线性表存储空间的初始分配量
#define LISTINCREMENT 10 // 线性表存储空间的分配增量
typedef struct{
ElemType *elem; //存储空间基址
int length; //当前长度
int listsize; //当前分配的存储容量(以sizeof(ElemType)为单位)
}SqList;
注:1.数组指针elem指示线性表的基地址;
2.length指示线性表的当前长度;
3.listsize指示顺序表当前分配的存储空间大小,一旦因插入元素而空间不足时,可进行再分配,即为顺序表增加一个大小为存储LISTINCREMENT个数据元素的空间。
2.初始化: 线性表的初始化操作就是为顺序表分配一个预定义大小的数组空间,并将线性表的当前长度设为0。算法如下:
Status InitList_Sq(SqList &L){
// 构造一个空的线性表
L.elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if(! L.elem)exit(OVERFLOW); //存储分配失败
L.length= 0; //空表长度为0
L.listsize= LIST_INIT_SIZE; //初始存储容量
return OK;
}// InitLiat_Sq
2.3-顺序表的插入与删除
在顺序存储结构的线性表中某个位置上插入或删除一个数据元素时,其主要时间耗费在移动元素上,移动元素的个数取决于插入或删除的位置。 插入和删除的时间复杂度为 O(n) 。
1. 插入:在第 i ( 1≤ i ≤n ) 个元素之前插入一个元素时,需要将第n位至第 i 个元素(共n-i+1个元素)向后移动一个位置,并将长度为 n 的顺序表变为长度为 n+1 的顺序表。算法如下:
Status ListInsert_Sq(List &L,int i,ElemType e){
//在顺序表 L的第 i个位置之前,插入元素 e
//i的合法范围是[1,ListLength.Sq(L)+1]
if(i < 1|| i > L.length + 1)return(ERROR); //i值不合法
if(L.length >= L.listsize){ //当前存储空间已满,增加分配
newbase = (ElemType *)realloc(L.listsize + LIST_INIT_SIZE * sizeof(ElemType));
if(!newbase)exit(OVERFLOW); //存储分配失败
L.elem = newbase; //新基址
L.listsize += LISTINCREMENT; //增加存储容量
}
q = &(L.elem(i-1)); //q为插入位置
for(p = &(L.elem[L.length-1]); p>=q; --p) *(p+1) = *p; //插入位置及之后的元素右移
*q = e; //插入e
++L.length; //表长增1
return OK;
}//ListInsert_Sq
易错点:1.注意判断存储空间是否已满,若已满则需要申请额外的空间。
2.代码第5行,判断存储空间是否已满,用的是 “>=” 而不是 “==”。 L.length 是当前顺序表中实际存储的元素个数,而 L.listsize 是当前分配的总容量(能存储的元素个数),理论上,L.length 的最大值就是 L.listsize,L.length > L.listsize 似乎不可能发生。但实际上,可能会因为其他地方的逻辑错误(比如一个bug导致 length 被错误地增加)导致 length 超过 listsize。如果使用 ==,这个 if 判断就会失效,程序会继续执行,最终可能导致在越界的位置写入数据,引发程序崩溃或数据损坏。所以这里使用“>=”是一种更健壮、更具防御性的编程。
Tips:注意第12行中 for 循环中的步进,使用的是 --p(前缀自减),第14行中用的也是 ++L.length(前缀自增),具体原因可以参考知乎这篇关于 i++ 和 ++i 的区别(https://zhuanlan.zhihu.com/p/391942337),简单来说在这里是实现的效果没区别的,但是 ++i 效率会稍微高一些(感慨一下,严蔚敏老师书里的算法代码真的健壮性太好了)。
2. 删除:删除第 i ( 1≤ i ≤n ) 个元素时,需要将第 i+1 至第 n 个元素(共 n-i 个元素)向后移动一个位置,并将长度为 n 的顺序表变为长度为 n-1 的顺序表。算法如下:
Status ListDelete_Sq(List &L,int i,ElemType e){
//在顺序表 L的第 i个元素,用 e返回其值
//i的合法范围是[1,ListLength.Sq(L)]
if(i < 1|| i > L.length)return(ERROR); //i值不合法
p = &(L.elem[i-1]); //p为被删除元素的位置
e = *p; //被删除的元素值赋给e
q = L.elem + L.length -1; //表尾元素的位置
for(++p; p <= q; ++p) *(p-1) = *p; //删除位置及之后的元素左移
--L.length; //表长减1
return OK;
}//ListDelete_Sq
同时提供一段笔者的 错误算法 ,以供警示:
Status ListDelete_Sq(List &L,int i,ElemType e){
//在顺序表 L的第 i个位置处,删除元素 e
//i的合法范围是[1,ListLength.Sq(L)]
if(i < 1|| i > L.length)return(ERROR); //i值不合法
for(p = &(L.elem[i]); p <= L.length-1; ++p) *p = *(p+1); //删除位置及之后的元素左移
--L.length; //表长减1
return OK;
}//ListDelete_Sq
错误的点在于:
1.指针 p 的初始位置错误。在C/C语言中,数组索引是从 0 开始的。如果用户想删除第 i 个元素,那它在数组中的实际位置是 i-1,而不是 i 。
2.使用++p(先使用再递增),循环体内部执行的是 *p = (p+1),会导致“先移动,再判断”。
3.循环终止条件错误。(L.length) 是数组第一个非法元素(L.elem[L.length])的内存,如果删除的是表中最后一个元素,会导致越界访问,表原来的最后一个位置读取并写入不可预测的垃圾数据,可能引发程序崩溃或数据损坏。
易错点:注意越界访问。删除操作的本质是“用后面的元素覆盖前面的元素”。当删除最后一个元素时,它后面已经没有元素了,所以不应该有任何覆盖操作。
2.4-顺序表的按值查找
在顺序表L中访查是否存在和e相同的数据元素的最简便的方法是,令e和L中的元素逐个比较,算法如下:
int LocateElem_Sq(SqList L,ElemType e,Status(* compare)(ElemType,Elemtype)){
//在顺序表 L中查找第一个元素值为 e的位序
//若找到,则返回其在 L中的位序,否则返回0
i=1;
p=L.elem;
while(i<=L.length && !(* compare)(* p++,e))++i;
if(i<=L.length) return i;
else return 0;
}//LocatteElem_Sq
1879

被折叠的 条评论
为什么被折叠?



