一、线性表的定义
线性表:是具有相同数据类型的n(n>=0)个数据元素的有限序列,其中n为表长,当n=0时,线性表是一个空表。 记作 ( a1, ..., ai, ai+1, ..., an ) 其中ai是线性表中的数据元素, n是表的长度
存在唯一的一个被称做“第一个”的数据元素 (如a1 )
存在唯一的一个被称做“最后一个”的数据元素 (如an )
除第一个数据元素外,其他元素均只有一个直接前驱
除最后一个数据元素外,其他元素均只有一个直接后继
特点:
1.表中元素的个数有限
2.表中元素具有逻辑上的顺序性,表中元素有其先后次序
3.表中元素都是数据元素,每个元素都是单个元素
4.表中元素的数据类型都相同,即每个元素都占有相同大小的存储空间。
5.表中元素具有抽象性,即仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容。
注:线性表是一种逻辑结构,表示元素之间一对一的相邻关系,顺序表和链表是指存储结构。
基本数据类型是一些不可再划分的数据,一般就是整形、浮点型、以及字符型。
抽象数据类型(ADT):指从问题中抽象出来的一个数据模型以及定义在此数据模型上的一组操作,不考虑计算机的具体存储结构与运算的具体实现算法。
ADT 抽象数据类型名{
D: 数据对象:<数据对象的定义>
S: 数据关系:<数据关系的定义>
P: 基本操作:<基本操作的定义>
} ADT 抽象数据类型名
描述线性表
ADT List {
数据对象:D = { ai | ai属于元素集,(i = 1,2,...,n,n>=0)}
数据关系:R = {
基本操作:
InitList(&L): 初始化表。构造一个空的线性表。
Length(L):求表长。返回线性表L的长度。
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
ListInsert(&L,i,e):插入操作。在表L中的第i个位置插入指定元素e。
ListDelete(&L,i,&e):删除操作。删除表L中第i个位置上的元素,并用e返回删除元素的值。
PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
Empty(L):判空操作。若L为空表,则返回true,否则返回false。
DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间。
}ADT List
二、顺序表的定义
顺序表:线性表的顺序存储叫做顺序表。
顺序存储结构:指的是用一段地址连续的存储单元依次存储线性表的数据元素。
可以用一维数组来实现顺序存储结构,即把第一个数据元素存到数组下标为0的位置中,接着把顺序表相邻的元素存储在数组中相邻的位置。
顺序表特点:表中元素的逻辑顺序与其物理顺序相同。
线性表的顺序存储结构是一种随机存取的存储结构。
线性表中元素的位序是从1开始的,而数组中元素的下标是从0开始的。
如何分配空间?
静态分配
1、 在程序编译或运行过程中,按事先规定大小分配内存 空间的分配方式。int a [10]
2、 必须事先知道所需空间的大小。
3、 分配在栈区或全局变量区,一般以数组的形式。
4、 按计划分配。
在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定 。
动态分配
1、在程序运行过程中,根据需要大小自由分配所需空间。
2、按需分配。
3、分配在堆区,一般使用特定的函数进行分配。
注:
malloc(m)函数,开辟m字节长度的地址空间,并返回这段空间的首地址
sizeof(x)运算,计算变量x的长度
free(p)函数,释放指针p所指变量的存储空间
假定线性表的元素类型为ElemType,则线性表的顺序存储类型描述为:
#define MaxSize 50 //定义线性表的最大长度
typedef struct{
ElemType data [MaxSize]; //顺序表的元素
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义
void InitList(SeqList& L) {
L.elem=new ElemType[MAXSIZE];
//L.data[0] = 0;
// L.data[1] = 0;
L.Length = 0 ;
printf("顺序表初始化成功!\n");
}
注:typedef int ElemType;//定义ElemType为int类型,想让它是什么类型,用typedef重定义就行。
一维数组可以是静态分配,也可以是动态分配。
静态分配时:由于数组的大小和空间事先已经固定,一旦空间占满,再加入新的数据就会产生溢出,进而导致程序崩溃。
动态分配时:存储数组的空间是在程序执行过程中通过动态存储分配语句分配的,一旦数据空间占满,就另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储数组空间的目的。
#define InitSize 10 //定义顺序表的初始长度
typedef struct{
ElemType *data; //指示动态分配数组的指针,也就是malloc函数返回的分配空间的起始地址
int MaxSize; //顺序表的最大容量
int length; //顺序表当前长度
}SeqList;
void InitList(SqList& L) {
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize); //C的初始动态分配语句
//L.data=new ElemType[InitSize]; //C++的初始动态分配语句
L.MaxSize = InitSize;
L.length = 0;
printf("顺序表初始化成功!\n");
}
动态分配存储空间时,如果原先分配的存储空间不足时可以继续申请空间。按照之前设置的增量来增加存储容量。
int* newbase = (int*)realloc(L.data, sizeof(int) * (InitSize + ListIncrement));
指针名=(数据类型*)realloc(要改变内存大小的指针名,新的大小),在原有的空间上增加空间
功能:先判断当前的指针是否有足够的连续空间,如果有,扩大指向的地址,并且将地址返回,如果空间不够,先按照新的大小指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
注:动态分配物理结构没有变化,依然是随机存取方式,只是分配的空间大小可以在运行时动态决定。
三、顺序表的基本操作
初始化顺序表L (参数用引用)
Status InitList_Sq(SqList &L){ //构造一个空的顺序表L
L.elem=new ElemType[MAXSIZE]; //为顺序表分配空间
if(!L.elem) return error; //存储分配失败
L.length=0; //空表长度为0
return OK;
}
初始化顺序表L (参数用指针)
Status InitList_Sq(SqList *L){ //构造一个空的顺序表L
L-> elem=new ElemType[MAXSIZE]; //为顺序表分配空间
if(!L.elem) return error; //存储分配失败
L-> length=0; //空表长度为0
return OK;
}
销毁线性表L
void DestroyList(SqList &L){
if (L.elem) delete L.elem; //释放存储空间 等同于free(L.elem);
}
清空线性表L
void ClearList(SqList &L){
L.length=0; //将线性表的长度置为0
}
求顺序表L的长度
int GetLength(SqList L){
return L.length;
}
判断顺序表L是否为空
int IsEmpty(SqList L){
if (L.length==0)
return 1;
else
return 0;
}
获取元素值(根据位置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;
}
在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(Sqlist L,ElemType e){
int i;
for((i=0;i if(L.data[i]==e) return i+1; //下标为i的元素值为e,返回其位序i+1 } return 0; } 最好情况:查找的元素就在表头,仅需比较一次,时间复杂度为O(1) 最坏情况:查找的元素在表尾(或不存在)时,需要比较n次,时间复杂度为O(n) 平均情况:需要比较的次数为(n+1)/2 时间复杂度为O(n) 插入元素(在顺序表L中第i个数据元素之前插入数据元素e ) 最好情况:在表尾插入(即i=n+1),元素后移语句将不执行,时间复杂度为O(1) 最坏情况:在表头插入(即i=1),元素后移语句将执行n次,时间复杂度为O(n) 平均情况:需要移动的次数为n/2 时间复杂度为O(n) 删除元素(将顺序表L中第i个数据元素删除) 将被删元素赋给引用变量e Status ListDelete(SqList &L,int i,Elemtype &e){ if(i<1||i>L.length) return false; e=L.data[i-1]; //将被删除的元素赋值给e for(int j=i;j L.data[j-1]=L.data[j]; L.length--; //线性表长度减1 return true; } 最好情况:删除表尾元素(即i=n),无须移动元素,时间复杂度为O(1) 最坏情况:删除表头元素(即i=1),需移动除表头元素外的所有元素,时间复杂度为O(n) 平均情况:需要移动的次数为(n-1)/2,时间复杂度为O(n) 查找、插入、删除算法的平均时间复杂度为O(n) 顺序表特点: 优点:
(1)判断插入位置i 是否合法。
(2)判断顺序表的存储空间是否已满。
(3)将第n至第i 位的元素依次向后移动一个位置,空出第i个位置。
(4)将要插入的新元素e放入第i个位置。
(5)表长加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;
}
(1)判断删除位置i 是否合法(合法值为1≤i≤n)。
(2)将第i+1至第n 位的元素依次向前移动一个位置。
(3)表长减1,删除成功返回OK。
顺序表的空间复杂度S(n)=O(1)(没有占用辅助空间)
存储密度大(结点本身所占存储量/结点结构所占存储量)
可以随机存取表中任一元素
缺点:
在插入、删除某一元素时,需要移动大量元素
表长变化大时候,存储空间难以确定