参考教材:数据结构C语言版(严蔚敏,吴伟民编著)
目录
线性表的基本操作:
1:线性表L的初始化(参数用引用)
2:销毁线性表L
3:清空线性表L
4:求线性表L的长度
5:判断线性表是否为空
6:顺序表的取值(根据位置i获取相应位置数据元素的内容)
7:线性表的查找
8:顺序表的插入
9:顺序表的删除
线性表的基本操作:
补充:操作算法中用到的预定义常量和类型
//函数结果状态代码
#define TURE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//Status是函数的类型,其值是函数结果状态的代码
typedef int Status;
typedef char ElemType;
1:线性表L的初始化(参数用引用)
Status InitList_Sq(SqList &L){ //构造一个空的顺序表L
L.elem=new ElemType[MAXSIZE]; //为顺序表分配空间
if(!L.elem) exit(OVERFLOW);//存储分配失败
L.length=0; //空表长度为0
return Ok;
}
new表示动态分配空间,即空间可伸缩
这个函数是初始化线性表,1.给数组分配空间,并检测分配是否成功 2.为长度赋值为0
if(!L.elem):这是一个条件语句,检查线性表L中的elem’成员是否为NULL或者未分配。’!是逻辑非运算符,所以’!L.elem”的意思是如果L.elem'为NULL或者未分配,条件成立。
c的写法是(elem type*)malloc(sizeof(elemtype)*maxsize;
2:销毁线性表L
void DestroyList(SqList &L){
if(L.elem) delete L.elem;//释放存储空间
}
销毁:从内存中将L占用的空间释放
前提条件:有空间才才释放,没有空间不需要释放
if(L.elem) delete L.elem:如果这个线性表存在,然后将他删除掉,线性表L将不再存在。
3:清空线性表L
vold ClearList(SqList &L){
L.length=0; //将线性表的长度置为0
}
这个就属于逻辑上的清空,让之前的数据找不到了就叫做清空。
逻辑清空,假装已经清空了,只要把长度变成0,那么后面的访问超出范围就会主动拒绝掉。
4:求线性表L的长度
int GetLength(SqList L){
return (L.length);
}
这里可以加&,这样当列表很大时可以节约开辟副本的时间和空间。
5:判断线性表是否为空
int IsEmpty(SqList L){
if (L.length==0) return 1;
else return 0;
}
if (L.length==0):判断线性表是否为空,如果是空的则返回1,不是空的返回0
6:顺序表的取值(根据位置i获取相应位置数据元素的内容)
int GetElem(SqList L,int i,ElemType &e){
if (i<1||i>L.length) return ERROR;//判断i值是否合理,若不合理,返回ERROR
e=L.elem[i-1];//第i-1的单元存储着第i个数据
return Ok;
}
不引用是因为,不需要修改,牢记一点:是否需要对这个数据进行修改?不需要修改那么不需要引用。
只要记住,引用&的目的是修改这个数据,不修改就不引用,这个要点可以解答你所有有关“为什么&,为什么不&”的问题
i是需要获取第i个元素,e代表要取出的元素,这里用&引用直接会修改传入的变量e
这里i是位序,逻辑位序,物理位序的话是数组的下标,差了1,所以逻辑上的i,实际上是数组里的i-1
判断里是逻辑序号1,2,...,length, e的赋值语句里是物理序号0,1,,...,length-1,也可以把判断换成i<0||i>L.length-1
因为每个语句只执行一次,此处的时间复杂度为常量阶:O(1)
线性表的常用的两种储存结构:顺序存储结构(顺序表)和链式存储结构(链表)
7:线性表的查找
1:在线性表L中查找与指定值e相同的数据元素的位置
2:从表的一端开始,逐个进行记录的关键字和给定值的比较。如果找到,返回该元素的位置序号,未找到,返回0。
int LocateELem(SqList L, ElemType e){
//在线性表L中查找值为e的数据元素,返回其序号(是第几个元素)
for (i=0;i< L.length;i++)
if(L.elem[i]==e) return i+1; //查找成功,返回序号
return 0; //查找失败,返回0
}
这里面elem如果是自定义类型,是不可以做相等判断的,需要重载==
l.length就是元素的个数,因为下标从0开始,所以i=0,从0开始查找
i是对比数组里下标为i的数,数组的实际使用个数=l.length,但是下标是length-1.
此算法的执行次数最多的语句是: if(L.elem[i]==e) return i+1; 时间复杂度:O(n)
int LocateELem(SqList L, ElemType e){
//在线性表L中查找值为e的数据元素,返回其序号(是第几个元素)
while (i< L.length&&L.elem[i]!=e) i++;
if(i<L.length) return i+1; //查找成功,返回序号
return 0; //查找失败,返回0
}
顺序表的查找分析:
因为查找算法的基本操作为:将记录的关键字同给定值进行比较
基本操作:L.elem[i] == e
比较次数,e=a,1次;e=b,2次;e=c,3次;......,e=g,7次
平均查找次数:(1+2+3+4+5+6+7)/7 =4次 (首相加末项)/2=(1+7)/2
最好情况:查找的元素就在表头,仅需比较一次,时间复杂度为O(1)。
最坏情况:查找的元素在表尾(或不存在)时,需要比较n次,时间复杂度为O(n)
平均查找长度ASL(Average Search Length)
为确定记录在表中的位置,需要与给定值进行比较的关键字的个数的期望值叫做查找算法的平均查找长度。
对含有n个记录的表,查找成功时:
Pi:第i个记录被查找的概率 Ci:找到第i个记录需比较的的次数
顺序查找的平均查找长度:
假设每个记录的查找概率相等:则
假如,则
8:顺序表的插入
线性表的插入运算是指在表的第 i (1≤i<n(L.length)+1)个位置上,插入一个新结点(元素) e,使长度为n(L.length)的线性表(a1,… ai -1,ai,… an)变成长度为n+1(L.length+1) 的线性表(a1, .. ai -1,e, ai,an)。
算法思想:①判断插入位置i 是否合法。
②判断顺序表的存储空间是否已满,若已满返回ERROR。 ③将第n至第ì 位的元素依次向后移动一个位置,空出第i个位置 ④将要插入的新元素e放入第i个位置。 ⑤表长加1,插入成功返回OK。
Status ListInsert_Sq(SqList &L,int i ,ElemType e){
if(i < 1 || i > L.Length+1)return ERROR; //i值不合法
if(L.length == MAXSIZE) return ERROR; //当前存储空间已满
for(j = L.length-1; j >= i-1; j--)
L.elem[j+1]=L.elem[j]; //插入位置及之后的元素后移
L.elem[i-1]=e; //将新元素e放入第i个位置
L.length++; //表长增1
return OK;
}
if(i < 1 || i > L.Length+1)return ERROR:判断i的范围是否有效,i只能在1~L.length+1的范围,如果i不在规定范围:i<1或i > L.Length+1,则返回error。注意:这里的i是位序 不是组数下标!
if(L.length == MAXSIZE) return ERROR:判断当前的存储空间是否已满,L.length == MAXSIZE:当前存储空间已满,不能插入,则返回error
for(j = L.length-1; j >= i-1; j--)
L.elem[j+1]=L.elem[j]; :j是数组下标,将第i个元素及之后的元素后移,将下标为j的元素放入到j+1p要小于等于n+1就是只能紧贴着原数组的最后一个元素去插入,因为线性表规定所有元素必须紧挨在一起,不能出现空一个的情况
算法时间主要耗费在移动元素的操作上:
若插入在尾结点之后,则根本无需移动(特别快); 时间复杂度:O(1)
若插入在首结点之前,则表中元素全部后移(特别慢); 时间复杂度:O(n)
若要考虑在各种位置插入(共n+1种可能)的平均移动次数,该如何计算?
i+x=n+1,x=n+1-i i是第几个位置,x是移动次数
i~n总共移动x次,也就是总共x个元素,移动后i~n下标变成i+1~n+1,所以前面i个元素与后面x个元素的和为n+1。
i是位序(插到第几位),n是下标,x是移动次数,例:数组有5个元素,把新元素添插在最后就是第6;代入公式:6+0=5+1。
再来一个理解方法:n+1是插入新元素数组的最后一个位置,i是插入元素的位置,移动次数就是[最后-当前]=[n+1-i]
设pi代表在第i个位置上插入一个结点的概率,那么pi=1/(n+1),所以在长度为n个结点的顺序表中插入结点时所需要移动的平均次数可以这样表示·:
顺序表插入算法的平均时间复杂度:O(n)
插入不同位置的算法演示:
插入位置在最后:
插入位置在中间:
实际操作的时候, 先判断数组是否满, 满就返回错误。然后如果是在中间插入, 当然需要挪元素, 需要挪最后一个元素,最后一个元素的位序是 length-1 , 然后挪到length的位置
插入位置在最前面:
可以遍历整一个逆序的数组,然后在最后面插入一个元素,然后再遍历逆序一下,回到原来的数组
9:顺序表的删除
线性表的删除运算是指将表的第 i (1 ≤ i ≤ n(L.length) )个结点删除
使长度为n 的线性表
变成长度为n-1的线性表
算法思想:
①判断删除位置i 是否合法(合法值为1≤i≤n)。不合法则返回ERROR ②将欲删除的元素保留在e中。
③将第i+1至第n 位的元素依次向前移动一个位置。
④表长减1,删除成功返回OK。
Status ListDelete_Sq(SqList &L,int i){
if((i < 1)||(i > L.length)) return ERROR; //i值不合法
for (j = i;j <= L.length-1; j+ +)
L.elem[j-1 ]= L.elem[j]; //被删除元素之后的元素前移
L.length- -; //表长减1
return OK;
}
Status ListDelete_Sq(SqList &L,int i):在线性表L上面删除第i个位置上的元素,删除的结果仍由线性表L保存
if((i < 1)||(i > L.length)) return ERROR:判断删除位置是否合法,如果是小于1或大于L.length,则不合法,返回ERROR,位置合法,进行下一步。L.length就是元素的个数。
for (j = i;j <= L.length-1; j+ +) :从删除的元素i的后继开始,一直到最后一个元素L.length。 L.length个元素在L.length-1的位置存储。
L.elem[j-1 ]= L.elem[j]:将后面一个元素,赋值到前一个位置
i理解为位置,j是下标,i位置是(1~n),j对应下标是(0~n-1),所以删第i个位置就是删下标i-1。这里是j等于i,是指位置序号,不是数组下标
这里包括前面的代码都是删除指定位置的元素而不是指定索引的元素。
算法时间主要耗费在移动元素的操作上:
若删除尾结点,则根本无需移动(特别快); 时间复杂度:O(1)
若删除首结点,则表中n-1个元素全部前移(特别慢); 时间复杂度:O(n) 若要考虑在各种位置删除(共n种可能)的平均移动次数,该如何计算?
i=1,2,3,...,n x(次数)=n-1,n-2,...,0 i和x的关系:n-i
将移动次数相加:((n-1) + (n-2)+...+(0)) / n
顺序表删除算法的平均时间复杂度为O(n)