本文基于严蔚敏《数据结构C语言版》所总结的顺序表笔记
线性结构的特点:
1)存在唯一的第一个数据元素 |
2)除第一个元素外,每一个数据元素均只有唯一一个前驱 |
3)除最后一个数据元素外,每一个数据元素均只有唯一一个后继 |
线性表中元素的个数n(n>=0)定义为线性表的长度,n=0为空表。非空表中每一个数据元素都有一个确定的位置。
线性表的特点:线性表是一个相当灵活的数据结构,长度可以根据需要增长缩短,即可以根据需求增加长度或缩短长度,可以插入或者删除数据元素。
线性表顺序表示和实现
顺序表示:用一组地址连续的的存储的那元依次存储线性表的数据元素。
线性表顺序表示中第i+1个数据元素的存储位置和第i个数据元素存储位置间隔为每个数据元素所需占用的L个存储单元
由此,确定了初始位置,线性表顺序存储中任意一位数据元素都可以随机存取。
线性表的顺序存储是一种随机存储的存储结构 。
为了方便,下文线性表的顺序存储结构均称为顺序表
线性表动态分配存储结构
为什么要动态分配?不同问题所需长度不同,并且为了防止出现空间不足,要设定动态增加的范围增加程序健壮性
什么是存储空间基址?该数据得到的内存分配的起始地址
//------------------------顺序表结构体定义---------------------------------
#define LIST_INIT_SIZE 100 //顺序表存储空间的初始分配量
#define LISTINCREMENT 10 //顺序表存储空间的分配增量
//注意,这里没有分号
typedef struct
{
ElemType* elem; // 存储空间基址
int length; // 当前长度
int listsize; // 当前分配的存储容量(以sizeof(ElemType)为单位)
} SqList; // 顺序表
数组指针elem指示顺序表的基地址,length指示顺序表的当前长度 。顺序表的初始化操作就是为顺序表分配一个预定义大小的数组空间,并且将顺序表的当前长度设置为0。listsize指示顺序表当前分配的存储空间大小,一旦因为插入元素而导致空间不足,可再增加一个大小为存储LISTINCREMENT个数据元素的空间.
构造顺序表
注:因为C语言数组下标从0开始,所以第i个元素是L。elem[i-1]。
// 1.构造一个最大容量为 LIST_INIT_SIZE的空顺序表
Status InitList(SqList& L)
{
L.elem = (ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));
//用malloc分配一段这么LIST_INIT_SIZE*sizeof(ElemType)多个字节的内存段,它返
回一个指向该内存段首字节的指针变量,然后把这个指针变量强制转换为ElemType*类
型,再把这个经转换的指针变量赋给L的elem成员,即顺序表的存储空间基址被设定为这么大。
if (!L.elem) //如果内存分配失败
{
exit(0); //返回错误值0
}
L.length = 0; //空表的当前长度设置为0
L.listsize = LIST_INIT_SIZE; //顺序表当前分配的存储空间大小是最大值100
return 1; //构造成功返回1
} // InitList
插入和删除元素
插入:从第i-1个数据元素和第i个数据元素之间插入或删除数据元素
删除:删除第i个数据元素
注:在第i个元素前插入一个元素,要将从最后一个元素开始直至第i个元素,一个一个向后移动一 个位置。
删除第i个元素,要从第i+1个元素直至最后一个元素,一个一个向前移动一个位置。
// 3.在顺序表L的第 i 个元素之前插入新的元素e
Status ListInsert(SqList& L, int i, ElemType e)
{
//传入数据为:线性表的引用,第i个元素,插入数据元素的值
//i的合法值为1<=i<=ListLength+1
ElemType * newbase, * p, * q;
//定义新的存储空间基址来防止空间不足
if (i<1 || i>L.length + 1) //如果i值不合法,返回错误值0
{
return 0;
}
if (L.length >= L.listsize) //如果存储空间满了,增加分配
{
newbase = (ElemType*)realloc(L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if (!newbase) //如果内存分配失败,返回错误值0
{
exit(0);
}
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 1; //插入结束,返回1
} // ListInsert
// 删除顺序表L的第 i 个元素,用e 返回其值 1
Status ListDelete(SqList& L, int i, ElemType& e)
{
//传入数据为:引用顺序表L,第i个元素,引用e返回其值
ElemType* p, * q; //指针p指向被删除的元素,q用来移动元素
if ((i < 1) || (i > L.length)) //i值不合法返回错误值
{
return 0;
}
p = &(L.elem[i - 1]); //p为被删除元素的位置
e = *p;//被删除元素赋值给p
q = L.elem + L.length - 1;//q指向表尾元素的位置
for (++p; p <= q; ++p)
{
* (p - 1) = *p; //被删除元素之后的元素左移
--L.length; //表长-1
}
return 1; //删除成功返回1
} // ListDelete
插入和删除算法的时间复杂度是O(n)。
合并算法
什么是非递减排序?意思是关键字递增序排列,但是并非单调递增(因为有重复的关键字)从小到大或者允许中间有相等的情形: 1,2,3,4,5:递增排列, 9,8,7,6,5:递减排列。 1,2,3,3,4,5,8,8:非递减排列, 9,8,7,7,6,5,5,2,1 : 非递增排列。
//----------------------顺序表的合并------------------------------------------
void MergeList(SqList La,SqList Lb,SqList &Lc)
{
//已知顺序表La和Lb的元素按照值非递减排列
//归并La和Lb得到新的顺序表Lc,Lc得元素也按值非递减排列
ElemType* pa, *pb;
pa=La.elem;//新的基地址pa
pb=Lb.elem;//新的基地址pb
Lc.listsize=Lc.length=La.length+Lb.length;//Lc得长度等于两个长度之和
pc=Lc.elem=(ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));//新顺序表Lc得基地址
if(!Lc.elem)
{
exit(0);//如果存储分配失败,返回0
}
pa_last = la.elem+La.length-1;//找到La得末端
pb_last = lb.elem+Lb.length-1;//找到Lb得末端
while(pa<=pa.last&&pb<=pb.last)//归并
{
if(*pa<=*pb)
{
*pc++=pa++;
}
else
{
*pc++=pb++;
}
}
while(pa<=pa_last)
{
*pc++=*pa++;//插入La得剩余元素
}
while(pb<=pb_last)
{
*pc++=*pb++;//插入La得剩余元素
}
}