线性结构的特点:
- 存在唯一的一个被称做“第一个”的数据元素;
- 存在唯一的一个被称做“最后一个”的数据元素;
- 除第一个之外,集合中的每个数据元素均只有一个前驱;
- 除最后一个之外,集合中每个数据元素均只有一个后继;
线性表:一个线性表是n个数据元素的有限序列。
线性表再物理结构的表示上可以分为(1)顺序表示和(2)链式表示。
(1)线性表的顺序表示:线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,比如C语言中的一位数组就是采用一组连续的地址空间表示的。
通常,称顺序存储结构(或顺序映像)的线性表尾顺序表。顺序表的特点是:为表中相邻的元素赋以相邻的存储位置。换句话说,以元素在计算机内“物理位置相邻”来表示线性表中数据元素之间的逻辑关系。每一个数据元素的存储位置都和线性表的起始位置相差常数个数据元素大小。由此,只要确定了存储线性表的起始位置,线性表中任一数据元素都可随机存取,所以线性表的顺序存储结构是一种随机存取的存储结构。
下面是线性表的动态分配顺序存储结构的C代码:
/*
线性表的动态分配顺序存储结构
*/
# define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
# define LISTINCREMENT 10 //线性表存储空间的分配增量
typedef struct{
ElemType* elem; //存储空间基址
int length; //当前长度
int listsize; //当前分配的存储容量
}SqList;
附上头文件(Status是函数返回值状态码,ElemType是存储的数据类型,这里我以int型为例):
# include <iostream>
using namespace std;
# define TRUE 1
# define FALSE 0
# define OK 1
# define ERROR 0
# define INFEASIBLE -1
# define OVERFLOW -2
typedef int Status;
typedef int ElemType;
初始化一个空的线性表:
/*
构造一个空的线性表L
*/
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;
}//InitList_Sq
在线性表中第i个位置之前插入元素e:
/*
在顺序表的第i个位置前插入元素e
*/
Status ListInsert_Sq(SqList &L, int i, ElemType e)
{
//i的合法值为1<=i<=L.length+1
if(i<1 || i>L.length+1)
return ERROR;
//是否需要增加分配
if(L.length >= L.listsize)
{
ElemType *newbase = (ElemType *)realloc(L.elem, (L.listsize+LISTINCREMENT)*sizeof(ElemType));
if(!newbase)//增加分配失败
exit(OVERFLOW);
L.elem = newbase;//新的基址
L.listsize += LISTINCREMENT;//增加存储容量
}
ElemType *q = &(L.elem[i-1]);//插入位置
for(ElemType *p=&(L.elem[L.length-1]); p>=q; --p)//插入点后的元素后移
*(p+1) = *p;
*q = e;//插入e
++L.length;//表长加1
return OK;
}//ListInsert_Sq
由以上算法可以看出,当在顺序存储结构的线性表中的某个位置上插入或删除(偷懒没写,与插入类似)一个数据元素时,其时间主要耗费在移动元素上(换句话说,移动元素的操作为预估算法时间复杂度的基本操作),而移动元素的个数取决于插入或删除元素的位置,若表长为n,则插入和删除算法的时间复杂度为O(n),除此之外,容易看出,顺序表的“求表长”和“取第i个数据元素”的时间复杂度均为O(1)。
在顺序表L中查访是否存在和e相同的数据元素:
/*
在顺序表L中查访是否存在和e相同的数据元素
*/
int LocateElem_Sq(SqList L, ElemType e, Status (*compare)(ElemType, ElemType))
{
//找到返回在L中的位序,否则返回0
int i = 1;
ElemType *p = L.elem;
while(i<=L.length && !(*compare)(*p++, e))
++i;
if(i<=L.length)
return i;
else
return 0;
}//LocateElem_Sq
这里的“Status (*compare)(ElemType, ElemType)”表示,形参compare是一个指针变量,它指向什么呢?它指向一个函数,什么函数呢?一个含有两个ElemType类型形参并且返回值为Status类型的函数。在前面我们定义了Status是int类型,ElemType为int类型,所以实质上这里可以写成int (*compare)(int, int),它表示这是指向一个含有两个整形形参并且返回值为int型的函数的形参compare,实参传的是满足条件的函数名,用*compare调用这个函数。这里我写的函数实参原型是:
int equal_Int(int a, int b)
{
if(a == b)
return TRUE;
else
return FALSE;
}
(想一想这里为什么不直接调用上面的函数,反而“多此一举”呢?)
合并顺序表:
/*
顺序表的合并
*/
void MergeList_Sq(SqList La, SqList Lb, SqList &Lc)
{
/*
已知顺序线性表La和Lb的元素按值非递减排列,归并La、Lb得到Lc,Lc的元素也按值非递减排列
*/
int i = 0, j = 0, k = 0;
Lc.length = La.length + Lb.length;//1
Lc.listsize = Lc.length;//2
Lc.elem = (ElemType *)malloc(Lc.listsize*sizeof(ElemType));//给Lc分配空间3
if(!Lc.elem)//分配失败4
exit(OVERFLOW);
while(i<=La.length-1 && j<=Lb.length-1)//归并
{
if(La.elem[i] <= Lb.elem[j])
{
Lc.elem[k] = La.elem[i];//5
++i;
++k;
}
else
{
Lc.elem[k] = Lb.elem[j];//6
++j;
++k;
}
}
while(i<=La.length-1)//插入La剩余的元素
{
Lc.elem[k] = La.elem[i];//7
++i;
++k;
}
while(j<=Lb.length-1)//插入Lb剩余的元素
{
Lc.elem[k] = Lb.elem[j];//8
++j;
++k;
}
/*
可将1234步改为InitList_Sq(Lc);
5678步改为ListInsert_Sq(Lc, k, i)或ListInsert_Sq(Lc, k, j)
*/
}//MergeList_Sq
(未排序的表需先进行排序,否则。恩,你懂得)
(如果是求两个集合的并集,则应该把相等的情况拿出来单独讨论)
链式线性表请查看我的下一篇博文:https://blog.youkuaiyun.com/black_carbon/article/details/81097659