第二章 线性表
线性结构是一个数据元素的有序(次序)集
线性结构的基本特征:
1.集合中必存在唯一的一个“第一元素”;
2.集合中必存在唯一的一个“最后元素”;
3.除最后元素之外,均有唯一的后继;
4.除第一元素之外,均有唯一前驱。
2.1线性表的类型定义
抽象数据类型线性表的定义如下:
ADT List{
数据对象:
D = {ai | ai∈ElemSet,i=1,2,.....n,n≥0}
{称n为线性表的表长;
称n=0时的线性表为空表。}
数据关系:
R1 = {<ai-1,ai>|ai-1,ai∈D,i=2,.......n}
{设线性表为(a1,a2,.....,ai,.....,an),
称i为ai在线性表中的位序。}
基本操作:
{结构初始化}
InitList(&L)
操作结果:构造一个空的线性表L。
{销毁结构}
DestroyList(&L)
初始条件:线性表L已存在。
操作结果:销毁线性表L。
{引用型操作}
ListEmpty(L)
初始条件:线性表L已存在。
操作结果:若L为空表,则返回TRUE,否则FALSE。
ListLength(L)
初始条件:线性表L已存在。
操作结果:返回线性表L中元素的个数。
PriorElem(L,cur_e,&pre_e)
初始条件:线性表L已存在。
操作结果:若cur_e是L的元素,但不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义。
NextElem(L,cur_e,&next_e)
初始条件:线性表L已存在。
操作结果:若cur_e是L的元素,但不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义。
GetElem(L,i,&e)
初始条件:线性表L已存在,1≦i≦LengthList(L)。
操作结果:用e返回L中的第i个元素的值。
LocateElem(L,e,compare())
初始条件:线性表L已存在,compare()是元素判定函数。
操作结果:返回L中第一个与e满足关系compare()的元素的位序,若这样的元素不存在,则返回值为0。
ListTraverse(L,visit())
初始条件:线性表L已存在。
操作结果:依次对L的每个元素调用函数visit()。一旦visit()失败,则操作失败。
{加工型操作}
ClearList(&L)
初始条件:线性表L已存在。
操作结果:将L重置为空表。
PutElem(L,i,&e)
初始条件:线性表L已存在,1≦i≦LengthList(L)。
操作结果:L中第i个元素赋值同e的值。
ListInsert(&L,i,e)
初始条件:线性表L已存在,1≦i≦LengthList(L)+1。
操作结果:在L的第i个元素之前插入新的元素e,L的长度增1。
ListDelete(&L,i,e)
初始条件:线性表L已存在且非空,1≦i≦LengthList(L)。
操作结果:删除L的第i个元素,并用e返回其值,L的长度减1。
}ADT List
2.2线性表类型的实现——顺序映象
用一组地址连续的存储单元依次存放线性表中的数据元素
a1 | a2 | .... | ai-1 | ai | .... | an |
|
以“存储位置相邻”表示有序对<ai-1,ai>
即:LOC(ai)=LOC(ai-1)+C
一个数据元素所占存储量↑
所有数据元素的存储位置均取决于第一个数据元素的存储位置
LOC(ai)=LOC(a1)+(i-1)*C
↑基地址
顺序映象的C语言描述
#define LIST_INIT_SIZE 80
//线性表存储空间的初始分配量
#define LISTINCREMENT 10
//线性表存储空间的分配增量
Typedef struct{
ElemType * elem;//存储空间基址
int length;//当前长度
int listsize;//当前分配的存储容量
//(以sizeof(ElemType)为单位)
}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;
}//InitList_Sq
线性表操作
LocateElem(L,e,compare())的实现:
int LocateElem_Sq(SqList L,ElemType e,Status(*compare)(ElemType,ElemType)){
i = 1;//i的初值为第一元素的位序
p = L.elem;//p的初值为第一元素的存储位置
while(i<=L.length && !(*compare)(*p++,e))
++i;
if(i <= L.length) return i;
else return 0;
}//LocateElem_Sq;
此算法的时间复杂度为O(ListLength(L));
线性表操作
ListInsert(&L,i,e)的实现:
Status ListInsert_Sq(SqList &L,int pos,ElemType e){
if(pos < 1|| pos > L.length + 1)return ERROR;//插入位置不合法
if(L.length >= L.listsize){//当前存储空间已满,增加分配
newbase = (ElemType *)realloc(L.elem,(L.listsize + LISTINCREMENT) * sizeof(ElemType));
if(!newbase)exit(OVERFLOW);//存储分配失败
L.elem = newbase;//新基址
L.listsize += LISTINCREMENT;//增加存储容量
}
q = &(L.elem[pos-1]);//q指示插入位置
for(p = &(L.elem[L.length-1]);p >=q;-- p)
*(p+1) = *p;//插入位置及之后的元素右移
*q = e;//插入e
++L.length;//表长增1
return OK;
}//ListInsert_Sq;
此算法时间复杂度为O(ListLength(L))。
线性表操作
ListDelete(&L,i,&e)的实现:
Status ListDelete_Sq(SqList &L, int pos, ElemType &e){
if((pos < 1)||(pos > L.length)) return ERROR;//删除位置不合法
p = &(L.elem[pos-1]);//p为被删除元素的位置
e = *p;//被删除元素的值赋给e
q = L.elem + L.length - 1;//表尾元素的位置
for(++p;p <= q;++p)
*(p-1) = *p;//被删除元素之后的元素左移
--L.length;//表长减1
return OK;
}//ListDelete_Sq;
此算法的时间复杂度为:O(ListLength(L))。
2.3线性表类型的实现——链式映象
一、单链表
用一组地址任意的存储单元存放线性表中的数据元素
以元素(数据元素的映象)+指针(指示后继元素存储位置的)=结点(表示数据元素)
以“结点的序列”表示线性表——称作链表
以线性表中第一个数据元素的存储地址作为线性表的地址,称作线性表的头指针
二、结点和单链表的C语言描述
Typedef struct LNode{
ElemType data;//数据域
Struct Lnode *next;//指针域
}LNode, *LinkList;
三、单链表操作的实现
线性表的操作GetElem(L,i,&e)
在链表中的实现:
基本操作位:使指针p始终指向线性表中第j个数据元素
Status GetElem L(LinkList L,int pos,ElemType &e){
p = L->next; j = 1;//初始化,p指向第一个结点,j为计数器
while(p && j<pos){p = p->next; ++j; }//p指针向后查找,直到p指向第pos个元素或p为空
if(!p || j>pos)
return ERROR;//第pos个元素不存在
E = p->data;//取第pos个元素
return OK;
}//GetElem_L;
此算法的时间复杂度为:O(ListLength(L))。
线性表的操作ListInsert(&L,i,e)在链表中的实现:
基本操作为:找到线性表中第i-1个结点,修改其指向后继的指针
Status ListInsert(LinkList &L, int pos, ElemType e){
p = L;j = 0;
while(p && j < pos-1)
{ p=p->next;++jl}//寻找第pos-1个结点
if(!p||j > pos - 1)
return ERROR;//pos小于1或者大于表长
s = (LinkList)malloc(sizeof(LNode));//生成新结点
s->data = e;s->next = p->next;//插入L
p->next = s;
return OK;
}//ListInsert_L
此算法的时间复杂度为O(ListLength(L))。
线性表的操作ListDelete(&L,i,&e)在链表中的实现:
基本操作为:找到线性表中第i-1个结点,修改其指向后继的指针
Status ListDelete(LinkList &L, int pos, ElemType &e){
p = L;j = 0;
while(p->next && j<pos-1)
{p = p->next;++j;}//寻找第pos个结点,并令p指向其前驱
if(!(p->next)||j > pos-1)
return ERROR;//删除位置不合理
q = p->next;p->next = q->next;//删除并释放结点
e = q->data; free(q);
return OK;
}//ListDelete_L;
此算法的时间复杂度为O(ListLength(L))。
初始化链表:
void CreateList_L(LinkList &L,int n){
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;//先建立一个带头结点的单链表
for(i=n; i>0; --i){
p = (LinkList)malloc(sizeof(LNode));
scanf(&p->data);//输入元素值。
p->next = L->next;L->next = p;//插入到表头。
}
}//CreateList_L
此算法的时间复杂度为:O(ListLength(L))。
求两个集合的并:
Void union(List &La,List Lb){
La _len = ListLength(La);
Lb_len = ListLength(Lb);
for(i = 1;i <= Lb_len;i ++){
GetElem(Lb,i,e);
if(!LocateElem(La,e,equal()))
ListInsert(La,++La_len,e)
}
}//union
上述算法的时间复杂度:
控制结构:for循环
基本操作:LocateElem(La,e,equal())
当以顺序映象实现抽象数据类型线性表时间复杂度为:O(La_len*Lb_len);
当以链式映象实现抽象数据类型线性表时间复杂度为:O(La_len*Lb_len);
有序合并:
void purge(List &La,List Lb){
InitList(LA);
La_len = ListLength(La);
Lb_len = ListLength(Lb);
for(i = 1;i <= Lb_len;i ++){
GetElem(Lb,i,e);
if(!equal(en,e)){
ListInsert(La,++La_len,e);
en = e;
}
}
}//purge
上述算法的时间复杂度:
控制结构:for循环
基本操作:GetElem(Lb,i,e);
当以顺序映象实现抽象数据类型线性表时间复杂度为:O(Lb_len);
当以链式映象实现抽象数据类型线性表时间复杂度为:O(La_len*Lb_len);
归并两个线性表:
Void MergeList(List La,List Lb,List &Lc){
InitList(Lc);
i = j = 1;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 <= bj){
ListInsert(Lc,++k,ai);
++i;
}
else{
ListInsert(Lc,++k,bj);
++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);
}
}
上述算法的时间复杂度为:
控制结构:三个并列的while循环
基本操作:ListInsert(Lc,++k,e)
当以顺序映象实现抽象数据类型线性表时间复杂度为:O(La_len+Lb_len);
当以链式映象实现抽象数据类型线性表时间复杂度为:O((La_len+Lb_len)*(La_len+Lb_len));
用上述定义的单链表实现线性表的操作时,
存在的问题:
1.单链表的表长是一个隐含的值;
2.在单链表的最后一个元素最后插入元素时,需遍历整个链表;
3.在链表中,元素的“位序”概念淡化,结点的“位置”概念强化。
改进链表的设置:
1.增加“表长”、“表尾指针”和“当前位置的指针”三个数据域;
2.将基本操作由“位序”改变为“指针”
四、一个带头结点的线性表类型
Typedef struct LNode{//结点类型
ElemType data;
struct LNode *next;
}*Link,*Position;
Status MakeNode(Link &p,ElemType e);
//分配由p指向的值为e的结点,并返回OK;
//若分配失败,则返回ERROR
void FreeNode(Link &p);
//释放p所指结点
Typedef struct{
Link head,tail;//指向头结点和最后一个结点
int len;//指示链表长度
Link current;
//指向当前访问的结点的指针
//初始位置指向头结点
}LinkList;
链表的基本操作:
{结构初始化和销毁结构}
Status InitList(LinkList &L);
//构造一个空的线性链表L
//头指针、尾指针和当前指针均指向头结点,表长为零
Status DestroyList(LinkList &L);
//销毁线性链表L,L不再存在
{引用型操作}
Status ListEmpty(LinkList L);//判表空
Int ListLength(LinkList L);//求表长
Status Prior(LinkList L);//改变当前指针指向其前驱
Status Next(LinkList L);//改变当前指针指向其后继
ElemType GetCurElem(LinkList L);//返回当前指针所指数据元素
Status LocatePos(LinkList L,int i);//改变当前指针指向第i个结点
Status LocateElem(LinkList L,ElemType e,Status(*compare)(ElemType,ElemType));
//若存在与e满足函数compare()判定后关系的元素,则移动当前指针
//指向第1个满足条件的元素,并返回OK;否则返回ERROR
Status ListTraverse(LinkList L,Status(*visit()));
//依次对L的每个元素调用函数visit()
{加工型操作}
Status ClearList(LinkList &L);//重置为空表
Status SetCurElem(LinkList &L,ElemType e);//更新当前指针所指数据元素
Status Append(LinkList &L,Link s);//一串结点链接在最后一个结点之后
Status InsAfter(LinkList &L,ElemType e);//将元素e插入到当前指针之后
Status DelAfter(LinkList &L,ElemType *e);//删除当前指针之后的结点
Status InsAfter(LinkList &L,ElemType e){
//当前指针在链表中,则将数据元素e插入在线性链表L中
//当前指针所指结点之后,并返回OK;否则返回ERROR。
if(!L.current)return ERROR;
if(!MakeNode(s,e))return ERROR;
S->next = L.current->next;
L.current->next = s;
return OK;
}//InsAfter
Status DelAfter(LinkList & L,ElemType &e){
//当前指针及其后继在链表中,则删除线性链表L中当前指针所指结点之后的结点
//并返回OK,否则返回ERROR。
if(!(L.current&&L.current->next))return ERROR;
q = L.current->next;
L.current->next = q->next;
e = q->data;
FreeNode(q);
return OK;
}//DelAfter
{利用上述定义的线性链表可以完成线性表的其他操作}
例一:
Status ListInsert_L(LinkList L,int i ,ElemType e){
//在带头结点的单链线性表L的第i个元素之前插入元素e
if(!LocatePos(L,i-1))return ERROR;//i使不合法
if(InsAfter(L,e))return OK;//插入第i-1个结点之后
else return ERROR;
}//ListInsert_L
例二:
Void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc,int(*compare)(ElemType,ElemType)){
if(!InitList(Lc))return ERROR;//存储空间分配失败
LocatePos(La,0);LocatePos(Lb,0);//当前指针指向头结点
if(DelAfter(La,e)) a = e;
else a = MAXC;//MAXC为常量最大值
if(DelAfter(Lb,e)) b = e;
else b = MAXC;//a和b为两表中当前比较元素
while(!(a = MAXC && b = MAXC)){//La或Lb非空
.......
}
DestroyList(La);DestroyList(Lb);//销毁链表La和Lb
return OK;
}//MergeList_L
if((*compare)(a,b) <= 0){//a <=b
InsAfter(Lc,a);
if(DelAfter(La,e1)) a = e1;
else a = MAXC;
}
else{//a > b
InsAfter(Lc,s);
if(DelAfter(Lb,e)) b = e1;
else b = MAXC;
}
五、其它形式的链表
1.双向链表
//-----线性表的双向链表存储结构----
Typedef sttuct DuLNode{
ElemType data;//数据域
struct DuLNode *prior;//指向前驱的指针域
struct DuLNode *next;//指向后继的指针域
}DuLNode,*DuLinkList;
2.循环链表
最后一个结点的指针域的指针又指回第一个结点的链表。
2.4一元多项式的表示
一元多项式
pn(x) = p0 + p1x + p2x2 +...+pnxn
在计算机中,可以用一个线性表来表示:
P = (p0,p1,.....,pn)
一般情况下的一元多项式可写成
Pn(x) = p1xe1 + p2xe2 +....+ pmxem
其中:pi是指数为ei的项的非零系数,
0 <= e1 < e2 <....< em = n
((p1,e1),(p2,e2),...(pm,em))
抽象数据类型一元多项式的定义如下:
ADT Polynomial{
数据对象:D = {ai | ai∈TeemSet, i = 1,2,3....,m, m≥0
TermSet中的每一个元素包含一个表示系数的实数和表示指数的整数}
数据关系:R1 = {<ai-1,ai> | ai-1,ai∈D,且ai-1中的指数值<ai中的指数值,i = 2,....,n}
基本操作:
CreatPolyn(&P,m)
操作结果:输入m项的系数和指数,建立一个一元多项式P。
DestroyPolyn(&P)
初始条件:一元多项式P已存在。
操作结果:销毁一元多项式P。
PrintPolyn(P)
初始条件:一元多项式P已存在。
操作结果:打印输出一元多项式P。
PolynLength(P)
初始条件:一元多项式P已存在。
操作结果:返回一元多项式P中的项数。
AddPolyn(&Pa,&Pb)
初始条件:一元多项式Pa和Pb已存在。
操作结果:完成多项式相加运算,即:Pa = Pa + Pb,并销毁一元多项式Pb。
SubtractPolyn(&Pa,&Pb)
初始条件:一元多项式Pa和Pb已存在。
操作结果:完成多项式相减运算,即:Pa = Pa - Pb,并销毁一元多项式Pb。
MultiplyPolyn(&Pa,&Pb)
初始条件:一元多项式Pa和Pb已存在。
操作结果:完成多项式相乘运算,即:Pa = Pa × Pb,并销毁一元多项式Pb。
}ADT Polynomial
抽象数据类型Polynomial的实现
Typedef struct{//项的表示,多项式的项作为LinkList的数据元素
float coef;//系数
int expn;//指数
}term,ElemType;//两个类型名:term用于本ADT,ElemType为LinkList的数据对象名
Typedef LinkList polynomial;//用带表头结点的有序链表表示多项式
int cmp(term a,term b);
//依a的指数值<(或=)(或>)b的指数值,分别返回-1、0和+1
Void CreatPolyn(polynomial &P,int m){
//输入m项的系数和指数,建立表示一元多项式的有序链表P
initList(P);
e.coef = 0.0; e.expn = -1;
setCurElem(P,e);//置头结点的数据元素
for(i = 1;i <= m; ++i){
//依次输入m个非零项
scanf(e.coef,e.expn);
if(!LocateElem(P,e,(*cmp)()))
//当前链表中不存在该指数项
InsAfter(P,e)
}
}//CreatPolyn