线性表
1.定义
有限的多个数据元素有序序列
2.抽象数据类型
ADT 线性表
Date
数据对象集合为{a1,a2,a3,…,an},每个元素类型为相同Datetype,除了第一个元素外,每个元素有且只有一个前置元素;除了最后一位元素外,每个元素有且只有一个后置元素,数据元素间是一对一的关系
Operation
初始化操作,建立一个空线性表
检验是否为空表
将线性表清空
根据位序,查找返回元素
根据元素,查找返回第一次出现位置
在指定位置前插入指定元素
删除指定位置元素
返回线性表长度
顺序存储结构
1.定义
用一段地址连续的存储单元顺序存储线性表的各元素
2.原理(描述)
描述一个顺序存储结构的线性表需要以下三个元素
(1)存储空间的起始位置
(2)存储空间的最大容量
(3)线性表的当前尾标或者长度
3.实现
(1)C实现
typedef struct Lnode * List;
struct Lnode{
ElenmentType Date[Maxsize];
int Last;
};
struct Lnode L;
List PtrL;//访问下标为i的元素:L.Date[i] 或者 PtrL->Date[i]
//线性表当前长度: L.Last+1或者PtrL->Last+1
[1]初始化线性表(建立空表)
List MakeEmpty()
{
List PtrL;
PtrL=(List)malloc(sizeof(struct Lnode));
Last=-1;
return PtrL;
}
[2]查找(按值寻位置)
int Find(ElementType X,List PtrL)
{
int i=0;
while((i<=PtrL->Last)&&(X!=PtrL->Date[i])){
++i;
}
if(i>PtrL->Last)
return -1;
else
return i;
}
[3]插入(第i个位置前插入元素X)
void Insert(List PtrL,ElementType X,int i)
{
if((i<1)||(i>(PtrL->Last+2))){
printf("invalue position\n");
return ;
}
if(PtrL->Last==Maxsize-1){
printf("full list\n");
return ;
}
for(int j=Ptrl->Last;j>=i-1;--j){
PtrL->Date[j+1]=PtrL->Date[j];
}
PtrL->Date[i-1]=X;
PtrL->Last++;
return ;
}
[4]删除(删除表中第i个位置的元素)
void Delete(List PtrL,int i)
{
if((i<1)||(i>(PtrL->Last+1))){
printf("invalue position\n");
return ;
}
for(j=i;j<=PtrL->Last;++j){
PtrL->Date[j-1]=PtrL->Date[j];
}
PtrL->Last--;
return ;
}
4.应用
链式存储结构
1.定义
不要求逻辑上相邻的两个元素物理上也相邻;通过“链”建立起数据元素之间的逻辑关系
2.原理(描述)
描述一个链式存储结构的线性表的每一个结点需要以下元素
(1)存放数据元素数据域
(2)存放后继结点的指针地址的地址域
单链表
1.定义
2.实现
(1)C实现
typedef struct Lnode *List;
struct Lode{
ElementType Date;
List Next;
};
struct Lode L;
List PtrL;
[1]求表长
int Length(List PtrL)
{
int i=0;
List P=PtrL;
while(P){
++i;
P=P->Next;
}
return i;
}
[2]按位置查找
List FindKth(int k,List PtrL)
{
List P=PtrL;
int i=1;
while(P!=NULL&&i<k){
P=P->Next;
++i;
}
if(i==k)
return P;
else
return NULL://上面四行可直接写成return P;
}
[3]按值查找
List Find(List PtrL,ElementType X)
{
List P=PtrL;
while(P!=NULL&&P->Date!=X){
P=P->Next;
}
return P;
}
[4]插入(在第i个结点后插入值为X的新结点)
List Insert(ElenmentType X,int i,List PtrL)
{
List pre_ptrl,now_ptrl;
if(i==1){
now_ptr=(List)malloc(sizeof(struct Lnode));
now_ptr->Next=PtrL;
now_ptr->Date=X;
return now_ptr;
}
pre_ptrl=FinKth(i-1,PtrL);
if(pre_ptrl==NULL){
printf("invalid i");
return NULL:
}
else{
now_ptr=(List)malloc(sizeof(struct Lnode));
now_ptr->Date=X;
now_ptr->Next=pre_ptr->Next;
pre_ptr->Next=now_ptr;
return PtrL;
}
}
[5]删除(删除第i个结点)
List Delete(List PtrL,int i)
{
List pre_ptr,now_ptr;
if(i==1){
now_ptr=PtrL;
if(PtrL!=NULL)
PtrL=PtrL->Next;
else
return NUll;
free(now_ptr);
return PtrL;
}
pre_ptrl=FinKth(i-1,PtrL);
if(pre_ptrl==NULL){
printf("invalid %d",i-1);
return NULL:
}
else if(pre_ptr->Next==NULL){
printf("invalid %d",i);
return NULL:
}
else{
now_ptr=pre_ptr->Next;
pre_ptr->Next=now_ptr->Next;
free(now_ptr);
return PtrL;
}
}
[6]单链表的整表创建
创建单链表的过程不像顺序结构线性表是预先分配好所占空间大小和位置,而是一个动态生成链表的过程
(1) 头插法–新结点放在上一个结点前面
void creatList_head (List *p,int n)//实参是头指针的指针,
//但该指针并没有指向任何结点
{
List ptr1;
int i;
srand(time(0));//初始化随机种子,当然也可以接收一个数组用于结点赋值
//建立一个带头结点的空链表
*p=(List)malloc(sizeof(struct Lnode));
*p->Next=NULL;//头结点数据域可以不放任何数据,当然一般放的是链表长度
for(i=0;i<n;++i){
ptr1=(List)malloc(sizeof(struct Lnode));
ptr1->Date=rand()%100+1;//结点数据域随机填充1~100的ElenmentType型数据
ptr1->Next=ptr1->next;
(*p)->next=ptr1;
}
}
(2) 尾插法–新结点放在上一个结点后面
void creatList_Tail (List *p,int n)//实参是头指针的指针
//但该指针并没有指向任何结点
{
List ptr1,ptr2;
int i;
srand(time(0));//初始化随机种子,当然也可以接收一个数组用于结点赋值
ptr1=*p;
//头结点数据域可以不放任何数据,头结点数地址域暂时不指向
for(i=0;i<n;++i){
ptr2=(List)malloc(sizeof(struct Lnode));
ptr2->Date=rand()%100+1;//结点数据域随机填充1~100的ElenmentType型数据
ptr1->Next=ptr2;
ptr1=ptr2;
}
ptr1->Next=NULL;
}
[7]删除整个单链表
void DeleteList(List *L)
{
List p,q;
p=(*L)->next;//L有头结点
//p=*L//L没有头结点时
while(p){
q=p->Next;
free(p);
p=q;
}
(*L)>Next=NULL;
}
3.应用
静态链表
1.定义
前面讲了单链表的实现,其插入与删除某数据元素时间复杂度为O(1),当然,这是在知道插入位置的前提下得到的,在整个过程中我们用到了结构体指针指向链表结点,然而,在许多高级语言中根本没有指针这一概念,所以就有人提出了一种方法:用数组实现链表,这就是静态链表,也称之为游标表示法
2.原理
静态链表的结点元素即数组中的每一个元素array[i],仍然由两个域组成
数据域data
数据域cur:这里和链表不同是,它存的不再是指向下一个节点的内存地址。而是下一个节点在数组中的下标
由上图我们需要注意以下几点:
我们对数组的第一个元素和最后一个元素做特殊处理,不存放数据。
把未使用的数组元素称为备用链表,使用了的成为数据链表。
数组的第一个元素(下标为0)的cur域存放备用链表第一个节点的下标。
数组的最后一个元素的cur域存放第一个有数据的节点的下标,相当于链表中头结点的存在。链表为空时,其值为0。
逻辑上就相当于数组分为两个带头结点的单链表:
[1] 存放数据元素的数据链表,其头结点为数组的最后一个元素
[2]暂未使用的数组元素的备用链表,其头结点为数组的第一个元素(有些地方为数组的第二个元素)
[3] 两个链表的最后一个结点的cur都为0(相当于链表中的NULL);
3.实现
(1) C语言
基于上面的理论,我们来看看代码是怎么描述这种存储结构的。
//---------线性表的静态单链表存储结构--------
#define MAXSIZE 1000 /*假设链表最大长度为1000*/
typedef struct StaticList *SLinkList;
struct StaticList
{
datatype data;
int cur; //为0时表示无指向
};
struct StaticList array[MAXSIZE];
SlinkList L;
[1]插入操作
前面我们讲动态链表的时候说过,增加和删除一个节点我们可以用malloc()和free()函数(C++可用new和delete)来实现。但是现在由于我们操作的是静态表,它可是用数组存的,可没有这种操作了。因此我们首先来自己实现一个静态表的malloc和free。
那么怎么辨别数组中哪些空间没有被使用呢?一个好的解决办法是,将所有未使用或者被删除的空间串成一个备用链表。插入节点时便可以从备用链表获取第一个未使用的空间的下标。因此我们在初始化的时候会做这样的工作:
//将所有节点链入备用链表
void SlistInit(SLinkList L)//接收结构体数组
{
int i;
for(i=0;i<MAXSIZE;i++){
L[i].cur=i+1;
}
L[MAXSIZE-1].cur=0;//z最后一个元素为数据链表的头结点,
//不指向任何地方,即cur为0,相当于动态链表中的NULL;
}
静态链表的插入就是 把节点 x 从备用链表中删除,然后将节点 x 插入数据链中
//获取备用链表中的第一个结点,并在备用链表中删除它
int Malloc_SL(Slinklist L)
{
int i=L[0].cur;
if(L[0].cur)//判断备用链表是否为空,若空,则L[0].cur==0;
//因为备用链表尾结点的游标值为0
{
L[0].cur=L[i].cur;//备用链接头结点指针指向第二个空结点
}
return i;
}
在链表第i个位置插入元素e(这个位置是指数据链表的位置)
void SlistInsert(Slinklist L,int i,ElemType X)
{
int k=MAXSIZE-1;
if(i<1||i>Slistlength(L)+1)//超出范围
return ;
//因为有头结点,所以无需单独考虑在第一个位置插入的情况
int j=Malloc_SL(L);//用j获取要插入备用链表第一个结点的位置
if(j)//只要备用链接点不为空
{
L[j].date=X;
//找第i个位置前面一个位置(的下标),即数据链表的第i-1个位置
//如何找?L[i-2].cur即为第i-1个位置
//找多少次? i-1次
for(int l=0;l<=i-2;++l)
k=L[k].cur;
L[j].cur=L[k].cur;
L[k].cur=j;
}
}
[2]删除操作
静态链表的删除就是 把节点 x 从数据链中删除,然后将节点 x 插入备用链表中
//将数据链表中待删除的位置回收到备用链表
void Free_SL(Slinklist L,int i)
{
L[i].cur=L[0].cur;
L[0].cur=i;
}
删除数据链表中的第i个位置的元素
void DeleteSlist(Slinklist L,int i)
{
if(i<1||i>Slistlength(L))//超出范围
return ;
//找到数据链表中的第i-1个位置
int k=MAXSIZE-1;
for(int j=0;j<=i-2;++j)
k=L[k].cur;
int j=L[k].cur;//数据链表中的第i个位置的下标
L[k].cur=L[j].cur;
Free_SL(L,j);
}
[3].静态链表L存在时的长度(不含头结点)
int SListLength(Slinklist L)
{
int i=L[MAXSIZE-1].cur;
int j=0;
while(i){
++j;
i=L[i}.cur;
}
return j;
}
4.应用
双向链表
1.定义
2.原理
3.实现
4.应用
循环链表
1.定义
2.原理
3.实现
4.应用