线性表:零个或多个数据元素的有限序列
元素之间是有序的,线性表是有限的,线性表元素个数为线性表长度,长度为0时称为空表。
ADT 线性表(List)
Data
线性表的数据对象集合为{a1,a2,…,an},每个元素的类型均为DataType。其中,除第一个元素a1外,每个元素有且只有一个直接前驱元素,除了最后一个元素an外,每个元素都有且只有一个直接后驱元素,数据元素之间的关系是一对一的关系。
Operation
InitList(*L); 初始化操作,建立一个空的线性表L。
ListEmpty(L); 若线性表为空,返回true,否则返回false。
ClearList(*L); 将线性表清空
GetElem(L,i,*e); 将线性表L中的第i个位置元素返回给e
LocateElem(L,e); 在线性表L中查找与给定值e相等的元素,如果成功,返回该元素在表中序号表示成功,否则,返回0表示失败。
ListInsert(*L,i,e); 在线性表L中的第i个位置插入新元素e
ListDelete(*L,i,*e); 删除线性表L中第i个位置元素,并用e返回其值
ListLength(L); 返回线性表L的元素个数
线性表的顺序存储结构,指的是用一段地址连续的存储单元一次存储线性表的数据元素
可以使用数组来实现顺序存储结构,即把第一个数据元素存到数组下标为0的位置中,接着把线性表向量的圆存储在数组中相邻的位置。
线性表的顺序存储的结果代码
typedef int ElemType; //ElemType类型根据实际情况而定,假设为int
typedef struct{
ElemType data[MAXSIZE]; //数组存储数据元素,最大值为MAXSIZE
int length; //线性表当前长度
}SqList;
顺序存储结构需要的三个属性:
数组长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的。
线性表的长度是线性表中数据元素个数
存储器中每个存储单元都有自己的编号,该编号称为地址。
LOC(ai)=LOC(a1)+(i-1)*c
通过这个公式,可以随时算出线性表中任意位置的地址,因此对每个线性表位置的存入或者去除数据,对于计算机来说都是相等时间,也即是时间复杂度为O(1)。
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
//Status是函数的类型,其值是函数结构状态代码,如OK等
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:用e返回L中第i个元数的值
Status GetElem(SqList L,int i,ElemType *e){
if(L.length==0 || i<1 || i>L.length)
return ERROR;
*e=L.data[i-1];
return OK;
}
思路:
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
Status ListInsert(SqList *L,int i,ElemType e)
{
int k;
if(L->length==MAXSIZE)
return ERROR;
if(i<1 || i>L->length+1)
return ERROR;
if(i<=L->length)
{
for(k=L->length-1;k>=i-1;k--)
L->data[k+1]=L->data[k];
}
L->data[i-1]=e;
L->length++;
return OK;
}
思路:
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:删除L的第i个元素,并用e返回其值,L的长度减1
{
int k;
if(L->length==0)
return ERROR;
if(i<1 || i>L->length)
return ERROR;
*e=L->data[i-1];
if(i<L->length)
{
for(k=i;k<L->length;k++)
L->data[k-1]=L->data[k];
}
L->length--;
return OK;
}
优点:
缺点:
为了表示每个数据元素ai与其直接后继元素ai+1直接的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个知识其直接后继的信息,存储数据元素信息的域称为数据域
,存储直接后继位置的域称为指针域。这两部分信息组成数据元素ai的存储映像,称为结点。
链表中第一个结点的存储位置称为头指针,有时为了方便对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点
头指针:
头结点:
常用以下示意图来表示链表:
//线性表的单链表存储结构
typedef struct Node
{
ElemType data;
struct Node *next;
} Node;
typedef struct Node *LinkList;//定义LinkList
获得链表第i个元素的思路:
//初始条件:链式线性表L已存在
//操作结果:用e返回L中第i个元素的值
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p;
p=L->next;
j=1;
while(p && j>i)
{
p=p->next;
++j;
}
if(!p || j>i)
return ERROR;
*e=p->data;
return OK;
}
单链表第i个元素插入结点的思路:
s->next=p->next; p->next=s;
//初始条件:链式线性表L已存在
//操作结果:在L中第i个位置之前插入新的元素e,L的长度加1
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinkList p;
LinkList s;
p=*L;
j=1;
while(p && j<i)
{
p=p->next;
++j;
}
if(!p || j>i)
return ERROR;
s = (LinkList)malloc(sizeof(Node));//生成新结点
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
单链表第i个元素删除结点的思路:
p-next=q->next
//初始条件:链式线性表L已存在
//操作结果:删除L的第i个元素,并用e返回其值,L的长度减1
Status ListDelete(LinkList *L,int i,ElemType *e)
{
int j;
LinkList p,q;
p=*L;
j=1;
while(p->next && j<i)
{
p=p->next;
++j;
}
if(!(p->next) || j>i)
return ERROR;
q=p->next;
p->next=q->next;
*e=q->data;
free(q);
return OK;
}
单链表整表创建的思路:
//随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)
void CreateListHead(LinkList * L,int n)
{
LinkList p;
int i;
srand(time(0)); //初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
for(i=0;i<n;i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand()%100+1;
p->next = (*L)->next;
(*L)->next = p;
}
}
//随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)
void CreateListTail(LinkList *L,int n)
{
LinkList p,r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;
for(i=0;i<n;i++)
{
p = (Node*)malloc(sizeof(Node));
p-data = rand()%100+1;
r->next = p;
r = p;
}
r-next = NULL;
}
单链表整表删除的思路:
//初始条件:顺序线性表L已存在,操作结果:将L重置为空表
Status ClearList(LinkList *L)
{
LinkList p,q;
p = (*L)->next;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
由此可得经验性结论:
若线性表需要频繁查找,很少进行插入和删除操作,采用顺序存储结构;
若需要频繁插入删除操作,采用单链表结构
若线性表中的元素个数变化较大或者根本不知道多大时,最好采用单链表结构
Basic、Fortran等早期的编程高级语言,没有指针,因此无法使用链表结构,因此使用数组来代替指针,用数组描述的链表叫做静态链表,也称为游标实现法。
线性表的静态链表存储结构
#define MAXSIZE 1000
typedef struct
{
ElemType data;
int cur;
} Component,StaticLinkList[MAXSIZE];
数组元素由两个数据域组成,data和cur,data用来存放数据元素,cur相当于单链表中的next指针,存放该元素后继在数组中的下标。
其中,将数组第一个和最后一个做特殊元素处理,不存数据,如图所示
上图的初始化数组状态如下所示
Status InitList(StaticLinkList spacr)
{
int i;
for(i=0;i<MAXSIZE-1;i++)
space[i].cur = i+1;
space[MAXSIZE-1].cur = 0;
return OK;
}
假设已经将数据存入静态链表,如“甲”,“乙”,“丙”,“丁”,“戊”,“己”,“庚”等数据,这静态链表如下所示
获取备用空间下标
//若备用空间链表非空,则返回分配的结点下标,否则返回0
int Malloc_SLL(StaticLinkList space)
{
//当前数组第一个元素cur存的值,即第一个备用空间的下标
int i = space[0].cur;
if(space[0].cur)
//下一个备用空间的下标
space[0].cur = space[i].cur;
return i;
}
插入操作
Status ListInsert(StaticLinkList L,int i,ElemType e)
{
int j,k,l;
k = MAXSIZE - 1;//最后一个元素的下标
if(i<1 || i>ListLength(L)+1)
return ERROR;
j = Malloc_SLL(L);//空闲下标
if(j)
{
L[j].data = e;
//找到第i个元素之前的位置
for(l=0;l<=i-1;l++)
k = space[k].cur;
//把第i-1个元素的cur值赋给新元素
L[j].cur = L[k].cur;
//把新元素的下标赋给第i-1个元素
L[k].cur = j;
return OK;
}
return ERROR;
}
//初始条件:静态链表L已存在。操作结果:返回L中的数据个数
int ListLength(StaticLinkList L)
{
int j=0;
int i=L[MAXSIZE-1].cur;
while(i)
{
i = L[i].cur;
j++;
}
return j;
}
当要在“乙”,“丁”之间插入“丙”时,输入i的值为3.
//删除在L中的第i个元素
Status ListDelete(StaticLinkList L,int i)
{
int j,k;
if(i<1 || i>ListLength(L))
return ERROR;
k = MAXSIZE-1;
for(j=1;j<=i-1;j++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SSL(L,j);
return OK;
}
//将下标为k的空闲结点回收到备用链表
void Free_SSL(StaticLinkList space,int k)
{
space[k].cur = space[0].cur;
space[0].cur = k;
}
优点:
将单链表中终端节点的指针端由空指针改为指向头结点,就使得整个单链表形成一个环,这种头尾相连的单链表称为单循环链表,简称循环链表
p = rearA->next;
rearA->next = rearB->next->next;
rearB->next = p;
free(p);
在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
typedef struct DulNode
{
ElemType data;
struct DulNode *prior;
struct DulNode *next;
} DulNode,*DulinkList;
如图所示,实现将结点s插入到结点p和p->next之间
代码实现
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
p->prior->next = p->next;
p->next-prior = p->prior;
free(p);