一个线性表是n个数据元素的有限序列
线性结构是一个数据元素的有序集:
- 集合中必存在唯一的一个“第一元素”
- 集合中必存在唯一的一个“最后元素”
- 除最后元素以外,均有唯一的后继
- 除第一元素之外,均有唯一的前驱
- 两线性表的并集
(扩大线性表La,将存在于线性表Lb中而不存在线性表La中的数据元素插入到线性表La中)
void Union(List&La,List Lb){
La_Len=ListLength(a);Lb_Len=ListLength(b);
for(i=1;i<=Lb_Len;i++){
GetElem(Lb,i,e);
if(!LocateElem(La,e,equal)) ListInsert(La,++La_Len,e);
}
}
- 非递增线性表的合并
void MergeList(List La,List Lb,List &Lc){
IniList(Lc);
i=j=0;k=0;
La_Len=ListLength(La);Lb_len=ListLength(Lb);
while(i<=La_Len&&j<=Lb.Len){
GetElem(La,i,ai);GetElem(Lb,j,bj);
if(ai<=aj) {
ListInsert(Lc,++k,ai);
i++;
}
else {
ListInsert(Lc,++k,bi);
j++;
}
}
while(i<=La_Len){
GetElem(La,i,ai);ListInsert(Lc,++k,ai);
}
while(j<=Lb_Len){
GetElem(Lb,j,bj);ListInsert(Lc,++k,bj);
}
}
假设GetElem和ListInsert这两个操作的执行时间和表长无关,则上述两个算法的时间复杂度分别为O(ListLength(La)*ListLength(Lb))和O(ListLength(La)+ListLength(Lb)
顺序存储
线性表的顺序表示是用一组地址连续的存储单元依次存储线性表的数据元素
(表中相邻的元素存储位置相邻)
顺序表支持随机存取
顺序映像——以x的存储位置和y的存储位置之间的某种关系表示逻辑关系
最简单的一种顺序映像方法是:令y的存储位置和x的存储位置相邻
- 线性表的动态分配顺序存储结构
#define List_Init_Size 100//线性表存储空间的初始分配量
#define LISTINCREMENT 10//线性表存储空间的分配增量
typedef int Status;
typedef struct{
int *elem;//存储空间基址
int length;//当前长度
int ListSize;//当前分配的存储容量
}SqList;
- 初始化顺序表
Status InitList_Sq(SqList &L){
L.elem=(ElemType*)malloc(List_Init_Size*sizeof(ElemType));
if(!L.elem) exit(OVERFLOW);
L.length=0;
L.ListSize=List_Init_Size;
return OK;
}
- 在第i个位置前插入一个元素
Status ListInsert(SqList&L,int i,int e){
int*p,*q;
if(i<0||i>L.length) return -1;
if(L.length>=L.ListSize){
p=(int*)realloc(L.elem,(L.ListSize+LISTINCREMENT)*sizeof(int));
if(p=NULL) return 0;
L.elem=p;L.ListSize+=LISTINCREMENT;
}
q=L.elem+i;
for(p=L.elem+L.length-1;p>=q;--p)
*(p+1)=*p;
*q=e;
++L.length;
return 1;
}
- 删除第i个位置上的元素
Status ListDelete(SqList&L,int i,int&e){
int*p,*q;
if((i<1)||i>L.length) return -1;
p=L.elem+i-1;
e=*p;
q=L.elem+L.length-1;
for(++p;p<=q;++p)
*(p-1)=*p;
--L.length;
return 1;
}
插入和删除元素的时间复杂度均为O(n)
对于顺序表而言,LocateElem_Sq的时间复杂度为O(L.Length),所以对于顺序表La,Lb而言Union的时间复杂度为O(La.Length*Lb.Length)
- 查找操作
int LocateElem_Sq(SqList L,ElemType e,Status(*compare)(ElemType,ElemType)){
//在顺序表中查找第一个值与e满足compare()的元素的位序
i=1;
p=L.elem;
while(i<=length&&!(*compare)(*p++,e)) ++i;
if(i<L.length) return i;
else return 0;
}
链式存储
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)
(表中相邻的元素物理上不一定相邻)
单链表是非随机存取的存储结构
以元素(数据元素的映像)+指针(指示后继元素存储位置)=结点(表示数据元素或数据元素的映像)
以“结点的序列”表示线性表称作链表
以线性表中第一个数据元素的存储地址作为线性表的地址,称作线性表的头指针
有时为了操作方便,在第一个结点之前虚加一个“头结点”,以指向头结点的指针作为链表的指针
- 线性表的单链表存储结构
typedef struct LNode{
int data;//数据域
struct LNode *next;//指针域
}LNode,*LinkList;
L:头指针(有头结点时指向头结点,否则指向第一个结点)
表为空表时头结点的指针域为空
单链表是一种顺序存取的结构,为找第i个数据元素,必须先找到第i-1个数据元素
因此,查找第i个数据元素的基本操作为:移动指针,比较j和i
- 在单链表中,取得第i个数据元素必须从头指针出发寻找
Status GetElem_L(LinkList L,int i,ElemType &e){
//L为带头结点的单链表的头指针
//当第i个元素存在时,将其赋值给e并返回OK,否则返回ERROR
p=L->next;j=1;//p指向第一个结点,j为计数器
while(p&&j<i){//顺指针向后查找,直到p指向第i个元素或者p为空
p=p->next;++j;
}
if(!p||j>i) return ERROR;
e=p->data;
return OK;
}
在链表中插入结点只需要修改指针。但同时,若要在第i个结点之前插入指针,修改的是第i-1个结点的指针域
基本操作:找到线性表中第i-1个结点,然后修改其指向后继的指针
s=(LinkList)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;p->next=s;
在链表中删除第i个结点的基本操作:找到线性表中第i-1个结点,然后修改其指向后继的指针
q=p->next;
p->next=q->next;
e=q->data;
free(q);
生成链表的过程是结点“逐个插入”的过程
- 头插法
void HeadCreatList(LinkList &L,int n)//头插法创建链表
{
L=(LinkList) malloc(sizeof(LNode));//申请一个头结点
L->next=NULL;//头指针为空
for(int i=n;i>0;--i){
LinkList p=(LinkList)malloc(sizeof(LNode));//p指向新申请的结点
scanf("%d",&(p->data));
p->next=L->next;//将节点p插入到头结点所在位置
L->next=p;//头结点前移
}
}
- 尾插法
该方法将新结点插入到当前链表的表尾上,为此必须增加一个尾指针,使其始终指向当前链表的尾结点
void TailCreatList(LinkList &L,int n) //尾插法创建链表
{
L=(LinkList) malloc(sizeof(LNode)); //申请一个头结点
L->next = NULL; //头指针为空
LinkList last = L; //last为指向尾结点的指针
for (int i = 0;i<n; i++)
{
LinkList p = (LinkList)malloc(sizeof(LNode)); //p指向新申请的结点
scanf("%d",&(p->data));
last->next = p;
last = p; //last指向尾结点
}
last->next = NULL;
}
如果我们在链表的开始结点之前附加一个结点(头结点):
1.由于开始结点的位置被存放在头结点的指针域中,所以在链表的第一个位置上的操作就和在表的其他位置上的操作已知,无需进行特殊处理
2.无论链表是否为空,其头指针是指向头结点的非空指针
- 打印链表
void PrintLinkList_L(LinkList &L){
LinkList p=L->next;//p指向第一个结点
while(p){
printf("%d ",p->data);
p=p->next;//指针的移动
}
printf("\n");
return ;
}
- 倒置链表
void Invert_L(LinkList &L){
LinkList p=L->next,q; //p指向第一个数据结点
L->next=NULL;//将原来链表置为空表
while(p){
q=p;
p=p->next;
q->next=L->next;//将当前结点插到头结点的后面
L->next=q;
}
}
循环链表
循环链表是以另一种形式的链式存储结构,它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环
和单链表的差别仅在于,判别链表中最后一个结点的条件不再是“后继是否为空”,而是“后继是否为头结点”
- 两个仅设尾指针的循环链表的合并
p=A->next;
q=B->next;
A->next=B->next->next;
B->next=p;
free(q);
A=B;
时间复杂度为o(1)
双向链表
有两个指针域,一个指向前驱,一个指向后继
d->next->prior=d->prior->next=d
//插入
s->data=e;
s->prior=p->prior;p->prior->next=s;
s->next=p;p->prior=s;
//删除
p->next=p->next->next;
p->next->prior=p;
小结:
顺序存储的优点:
1.方法简单,各种高级语言中都有数组,容易实现
2.不用为表示结点间的逻辑关系而增加额外的存储开销
3.顺序表具有按元素随机访问的特点
缺点;
1.在顺序表中做插入删除操作时,平均移动大约表中一半的元素,因此对n较大的顺序表效率低
2.需要预先分配足够大的存储空间,估计过大可能倒置顺序表后部大量限制,分配过小又会造成溢出
而链表的优缺点恰好与之相反