一 线性表的定义和基本操作
1.定义:线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,其中n表示表长,当n=0时线性表是一个空表。若用L命名线性表,
则其一般表示为 L=(a1,a2,a3,...,ai,...,an)。
几个概念:
①ai表示线性表的“第i个”元素线性表中的位序,位序一般是从1,开始的,而数组的下标是从0开始的。
②a1是表头元素,an是表尾元素。出第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继。
2.线性表的基本操作:
①InitList(&L):初始化表。构造一个空的线性表L,分配内存空间。
②DestoryList(&L):销毁操作。销毁线性表,释放线性表L所占用的内存空间。
③InsertList(&L,i,e):插入操作。在表L的第i个位置上插入指定元素e。
④DeleteList(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
⑤LocateElem(L,e):安置查找操作。在表L中查找给定关键字值的元素。
⑥GetElem(L,i):按位查找操作。获取表L中第i个位置元素的值。
其它操作:
①Length(L):求表长。返回线性表的长度,即L中数据元素的个数。
②PrintList(L):输出操作。按前后顺序输出线性表L的所有值。
③Empty(L):判空表。若L为空表,则返回true,否则返回false。
二 顺序表
1.定义:用顺序存储的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
2.顺序表的实现
①静态分配 :顺序表的表长刚开始确定后就无法更改(存储空间是静态的)
#define MaxSize 10; //定义最大长度
typedef struct {
ElemType data[MaxSize]; //用静态的数组存放数据元素
int length; //顺序表的当前长度
}SeqList;
②动态分配:顺序表满时,可用malloc动态拓展顺序表的最大容量,需要将数据元素复制到新的存储区域,并用free函数释放原区域。
#define InitSize 10; //顺序表的初始长度
typedef struct {
ElemType* data; //指示动态分配数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表当前的长度
}SeqList; //顺序表的类型定义(动态分配方式)
3.顺序表的特点:
①随机访问,即可以在O(1)时间内找到第i个元素。
②存储密度高,每个节点只存储数据元素
③扩展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
④插入、删除操作不方便,需要移动大量的元素。
4.顺序表上基本操作的实现(以静态分配为例)
①初始化线性表
void InitList(SqList &L)
{
for(int i=0; i<MaxSize; i++)
{
L.data[i] = 0; //将所有数据元素设置为默认初始值
}
L.length = 0; // 顺序表初始长度为0
}
初始化操作时间复杂度为:O(n)
②插入操作:在表L中的第i的位置上插入指定元素e
bool InsertList (SqList &L, int i, int e)
{
if(L.length >= MaxSize) //数组已经满了,不能插入元素了
{
return false;
}
if(i<1 || i>L.length+1) //位置从1开始,没有第0个位置;插入新的元素要紧挨着其它元素。
{
return false;
}
for(int j=L.length; j>=i; j++) //将第i个元素之后的的元素后移
{
L.data[j] = L.data[j-1];
}
L.data[i] = e; //在位置i放入e
L.length++; //顺序表的长度加1
return true; //插入成功
}
插入操作的时间复杂度:
最好情况:新元素插入到表尾,不需要移动元素,最好时间复杂度=O(1)
最坏情况:新元素插入到表头,需要将原有的n个元素全部后移,最坏时间复杂度=O(n)
平均情况:假设新元素插入到任何位置的概论相同,即i=1,2,3,...,length+1的概论都是p=1/(n+1).
i=1,循环n次;i=2,循环n-1次,...,i=n+1,循环0次,平均循环次数=np+(n-1)p+(n-2)p+...+1.p=n/2,则平均时间复杂度为O(n)
③删除操作:删除表L中第i个位置的元素
bool DeleteList(SqList& L, int i, ElemType& e)
{
if(i<0 || i>L.length) //判断i的范围是否有效
{
return false;
}
e = L.data[i-1]; //将删除的元素赋值给e
for(int j=i; j<L.length; j++) //将第i个位置后的元素前移
{
L.data[i-1] = L.data[i];
}
L.length--; //线性表长度减1
return true; //删除成功
}
删除操作的时间复杂度:
最好情况:删除表尾元素,不需要移动其它元素,最好时间复杂度=O(1)
最坏情况:删除表头元素,需要将后续的n-1个元素全部前移,最坏时间复杂度=O(n)
平均情况:推导同插入,平均时间复杂度=O(n)
④按位查找操作:获取表L中第i个位置的元素的值赋给e
bool GetElem(SeqList L, int i, ElemType& e)
{
if(i<1 || i>length) //判断范围是否有效
{
return false;
}
e = L.data[i-1]; //将查找到的值赋给e
return true; //查找成功
}
按位查找操作的时间复杂度:O(1) 。按位查找体现了顺序表随机存取的特性
⑤按值查找操作:在表L中查找具有给定关键字值的元素位置
int LocataElem(SqList L, ElemType e)
{
for(int i=0; i<L.length; i++)
{
if(L.data[i] == e)
{
return i+1; //数组下标为i的元素值等于e,返回位序i+1
}
}
return -1; //查找失败,返回-1
}
按值查找的时间复杂度:
最好情况:目标元素在表头,最好时间复杂度=O(1)
最坏情况:目标元素在表尾或目标元素不存在,最坏时间复杂度=O(n)
平均情况:如插入和删除一样分析,平均时间复杂度=O(n)
⑥销毁操作:静态分配下,计算机系统会自动分配内存空间和回收内存空间,故不需要执行销毁操作
一些其它操作
//求表长
int GetLength(SqList L)
{
return L.length;
}
//判空操作
bool EmtyList(SqList L)
{
return (L.data == 0);
}
//输出操作:按前后顺序输出线性表中所有值
void PrintList(SqList L)
{
for(int i=0; i<L.length; i++)
{
printf("%d",L.data[i]);
}
}
//输出操作时间复杂度:O(n)
志之所趋,无远弗届,
穷山距海,不能限也。