第二章 線性表
線性結構是一個數據元素的有序(次序)集
線性結構的基本特徵:
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