2.0 線性表
title: 資料結構 | 線性表
date: 2019-12-03 17:40:48
tags: 資料結構
線性表的順序和鏈式結構
線性連結串列、迴圈連結串列、雙向連結串列
主要內容
線性結構特點
在非空有限集內,存在唯一始末元素、除頭全有唯一前驅,除尾全有唯一後繼。
2.1 線性表的型別定義
2.2 線性表的順序表示與實現
2.3 線性表的鏈式表示與實現
2.3.1 線性連結串列
2.3.2 迴圈連結串列
2.3.3 雙向連結串列
線性表的邏輯結構
定義
一個線性表是n個數據元素的有限序列
特點
1.線性表中所有元素的性質相同。
2.除第一個和最後一個數據元素之外,其它資料元素有且僅有一個前驅和一個後繼。第一個資料元素無前驅,最後一個數據元素無後繼。
3.資料元素在表中的位置只取決於它自身的序號。
ADT
ADT List{ 資料物件:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0} 資料關係:R={<ai-1,ai>|ai,ai-1∈D,1=2,…,n} 基本操作: InitList( &L); DestroyList(&L); ClearList(&L); ListEmpty(L); ListLength(L); GetElement(L,i,&e); LocateElement(L,e,compare( )) PriorElement(L,cur_e,&pre_e) NextElement(L,cur_e,&next_e) ListInsert(&L,i,e); ListDelete(&L,i,&e); ListTraverse(L,visit( )) }ADT List
例題
線性表合併
假設有兩個集合A和B分別用兩個線性表LA和LB表示,現要求一個新的集合A=A∪B。
void union( List &La, List Lb) { // 將所有線上性表Lb中但不在La中的資料元素插入到La中 La_Len = ListLength( La ); // 求線性表的長度 Lb_Len = ListLength( Lb ); for( i = 1; i <= Lb_Len; i++) { GetElem( Lb, i, e); // 取Lb中第i個數據元素賦給e if( !LocateElem( La, e, equal)) ListInsert( La, ++La_Len, e); // La中不存在和 e 相同的資料元素,則插入之 } } // union
時間複雜度:
O(ListLength( La ) * ListLength( Lb ))
非遞減線性表La,Lb的合併
void MergeList( List La, List Lb, List &Lc )
{
InitList( Lc );
i = j = 1; // i和j分別是La和Lb的序號
k = 0; //k是Lc的序號
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 ){//若La非空,把La剩餘的資料元素插入到Lc中
GetElem( La, i++, ai );
ListInsert( Lc, ++k, ai );
}
while( j <= Lb_Len ){//若Lb非空,把La剩餘的資料元素插入到Lc中
GetElem( Lb, j++, bj );
ListInsert( Lc, ++k, bj );
}
}//MergeList
時間複雜度:
O(ListLength(La ) + ListLength( Lb))
線性表的順序儲存結構
順序表:用一組地址連續的儲存單元存放一個線性表
- 元素地址計算方法:
LOC(ai)=LOC(a1)+(i-1)*L
L—一個元素佔用的儲存單元個數
LOC(ai)—線性表第i個元素的地址 - 特點:
實現邏輯上相鄰—實體地址相鄰
實現隨機存取
順序表的型別定義
#define LIST_INIT_SIZE 100 // 線性表儲存空間的初始分配量
#define LISTINCREMENT 10 // 線性表儲存空間的分配增量
typedef struct{
ElemType *elem;//儲存空間基址
int length; //當前長度
int listsize //當前分配的儲存容量(以sizeof(ElemType)為單位)
}Sqlist;
順序表的重要操作
初始化順序表
Status InitList_Sq(SqList &L)
{ //構造一個空的順序表L
L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
if (! L.elem)
exit(OVERFLOW); //儲存分配失敗
L.length=0; //空表長度為0
L.listsize=LIST_INIT_SIZE; //初始儲存容量
Return OK;
}//InitList_Sq
順序表的插入操作
思路
1.輸入是否有效?
2.當前表是否已經滿?
3.移動 i 後的元素
4.插入元素
5.表長增1
程式碼
Status ListInsert_Sq(Sqlist &L, int i, ElemType e)
{ // 在順序線性表L的第i個位置之前插入新的元素e
// i的合法值為1<=i<=ListLength_Sq(L) + 1
if(i<1 || i>L.length+1)
return ERROR; //i值不合法
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[i-1]); // q為插入位置
for(p=&(L.elem[L.length-1]); p>=q; --p)
*(p+1)=*p; // 插入位置及之後的元素後移
*q=e; // 插入e
++L.length; // 表長增1
return OK;
}
順序表的刪除操作
思路
1.輸入是否有效?
2.刪除(前移元素)
3.表長減1
程式碼
Status ListDelete_Sq(SqList &L,int i,ElemType &e){
//在順序線性表L中刪除第.i個元素,並用e返回其值
//i的合法值為 1≤i≤L.length
if((i<1)||(i>L.Length))
return ERROR; // i值不合法或表空
p=&(L.elem[i-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
順序表的查詢操作
int LocateElem(SqList L,ElemType e, Status(*compare)(ElemType,ElemType)){
ElemType *p;
int i=1; // i的初值為第1個元素的位序
p=L.elem; // p的初值為第1個元素的儲存位置
while(i<=L.length&&!compare(*p++,e))
++i;
if(i<=L.length)
return i;
else
return 0;
}
順序表的遍歷(函式指標使用說明書)
- 初始條件:
順序線性表L已存在 - 操作結果:
依次對L的每個資料元素呼叫函式vi()。一旦vi()失敗,則操作失敗。
若在vi()的形參加'&',表明可通過呼叫vi()改變元素的值。
Status ListTraverse(SqList ,void(*vi)(ElemType&)){
ElemType *p;
int i;
p=L.elem;
for(i=1;i<=L.length;i++)
vi(*p++);
return OK;
}
順序表的一般操作
銷燬順序表
Status DestroyList_Sq ( SqList &L) {
if (!L.elem)
return ERROR; // 若表L不存在
free (L.elem); // 若表L已存在,回收動態分配的儲存空間
L.elem = null;
L.length = 0;
L.Listsize = 0;
return OK;
}// DetroyList_Sq
置空線性表
Status ClearList_Sq ( SqList &L) {
if (!L.elem)
return ERROR; // 若表L不存在
L.length = 0; //若表L已存在,將L置空
return OK;
}// ClearList_Sq
判斷空表
Status ListEmpty(SqList L)
{ // 初始條件:順序線性表L已存在。
//操作結果:若L為空表,則返回TRUE,否則返回FALSE
if(L.length==0)
return TRUE;
else
return FALSE;
}
求表長
int ListLength(SqList L){
//初始條件:順序線性表L已存在。
//操作結果:返回L中資料元素個數
return L.length;
}
取元素操作
Status GetElem_Sq ( SqList L, int i, ElemType &e ) {
if((i< 1)||(i>L.length))
return ERROR; // i 非法
e=L.elem[i-1]; //將順序表中第i 個元素賦值給 e
return OK;
}// GetElem_Sq
順序儲存結構的優缺點
優點
- 邏輯相鄰,物理相鄰
- 可隨機存取任一元素
- 儲存空間使用緊湊
缺點
- 插入、刪除操作需要移動大量的元素
- 預先分配空間需按最大空間分配,利用不充分
- 表容量難以擴充
線性表的鏈式儲存結構
特點
- 用一組任意的儲存單元儲存線性表的資料元素
- 利用指標實現了用不相鄰的儲存單元存放邏輯上相鄰的元素
- 每個資料元素ai,除儲存本身資訊外,還需儲存其直接後繼的資訊
- 結點{
資料域:元素本身資訊
指標域:指示直接後繼的儲存位置
}
線性連結串列的定義
結點中只含一個指標域的連結串列叫線性連結串列,也叫單鏈表。
typedef struct LNode {
ElemType data;
struct LNode *next;
}Lnode,*LinkList;
LNode* p 和 LinkList p
意思一樣,都是建立一個Lnode型的單鏈表
定義出來的都是1個Lnode型的指標變數,通常用他指向頭結點
特別地,注意
LinkList P,Q; /P,Q都是指標 /
LNode P,Q;/只有P是指標* /
- 頭結點
在單鏈表第一個結點前附設一個結點叫頭結點
頭結點指標域為空表示線性表為空
連結串列的重要操作
初始化連結串列
Status InitList_L (LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
if (!L)
exit(OVERFLOW);
L->next = null;
Return OK;
}// InitList_L
連結串列的按值查詢
Status LocateNode_L(LinkList L,Elemtype key,LNode &e){
p=L–>next;
while( p && p–>data!=key)
p=p–>next;
if(!p)
return ERROR;
e=p;
return OK;
}
連結串列的插入操作
思路
1.尋找第i-1個結點:
順指標向後查詢,直到p指向第i-1個元素或p->next為空
2.分配新空間
3.插入節點
程式碼
Status ListInsert_L(LinkList &L, int i, ElemType e){
//在帶頭結點的線性連結串列L中第i元素結點之前插入元素e
p=L; j=0
while (p&&j<i-1){
p=p->next;
++j;
}//☆尋找第i-1個元素結點
if(!p||j>i-1)
return ERROR; // i小於1 則 j>i-1
// i大於表長+1 則p為空
s=(LinkList)malloc(sizeof(LNode)); //分配新結點
s->data=e;
s->next=p->next; p->next=s; //插入新結點
return OK;
}//LinstInsert_L
連結串列的刪除操作
思路
1.尋找第i-1個結點:
順指標向後查詢,直到p指向第i-1個元素或p->next為空
2.刪除結點(修改其後繼指標)
3.回收(釋放)結點空間
程式碼
Status ListDelete_L(LinkList &L, int i, ElemType &e){
//在帶頭結點的單鏈線性表L中,刪除第i個元素,並由e返回其值
p=L; j=0;
while (p->next&&j<i-1){ //尋找第i-1個結點
p=p->next; ++j;
}
if(!(p->next)||j>i-1)
return ERROR; // 表中無第i個結點(i不合法)
// i<1 則 j>i-1
// i>表長 則 p->next為空
q=p->next;p->next=q->next; //刪除結點
e =q->data; free(q); // 釋放結點空間
return OK;
}//LinstDelete_L
連結串列的一般操作
取元素操作
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; //第i個元素不存在
e=p->data; //取第i個元素
return OK;
}//GetElem_L
頭插法建立單鏈表
思路
① 建立新節點
② 向新節點中添入內容
③ 使新節點指向鏈頭(第一個元素)
④ 改變頭指標,指向新節點
程式碼
void CreateList_L(LinkList &L, int n) {
//逆序輸入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
頭插法的幻想圖插了一個想不明白就想兩個節點
尾插法建立單鏈表
思路
① 建立新節點
② 向新節點中添入內容
③ 將新節點鏈入鏈尾
④ 改變尾指標
程式碼
void CreateList_L(LinkList &L, int n) {
//輸入n個元素的值,建立帶表頭結點的線性連結串列
L=(LinkList)malloc(sizeof (LNode));
L->next=NULL; //先建立一個帶頭結點的單鏈表
r=L;
for (i=1; i<=n;i++){
p=(LinkList)malloc(sizeof(LNode));//生成新結點
scanf(&p->data);//輸入元素值
p->next=NULL;
r->next=p;
r=p;
}
}//CreateList_L
歸併2個有序連結串列
void MergeList_L(LinkList &La, LinkList &Lb, LinkList &Lc){
pa=La->next;
pb=Lb->next;
Lc=pc=La; //用La的頭結點作為Lc的頭結點
while(pa && pb){
if (pa->data<=pb->data){
pc->next=pa;
pc=pa;
pa=pa->next;
}
else {
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
pc->next=pa?pa:pb;//插入剩餘段
free(Lb);//釋放Lb的頭結點
}//MergeList_L
單鏈表結構的優缺點
優點
- 動態結構,整個儲存空間為多個連結串列共用
- 不需預先分配空間
缺點
- 指標佔用額外儲存空間
- 不能隨機存取,查詢速度慢
迴圈連結串列
- 在單鏈表中,將終端結點的指標域NULL改為指向表頭結點的或開始結點,就得到了單鏈形式的迴圈連結串列,並簡單稱為單迴圈連結串列。
- 為了使空表和非空表的處理一致,迴圈連結串列中也可設定一個頭結點。
- 由於迴圈連結串列中沒有NULL指標,故涉及遍歷操作時,其終止條件就不再像非迴圈連結串列那樣判斷p或p—>next是否為空,而是判斷它們是否等於頭指標。
雙向連結串列
結構定義
typedef struct DulNode {
ElemType data;
struct DuLNode *prior;
struct DuLNode *next;
}DuLNode, *DuLinkList;
插入結點程式
Status ListInsert_DuL(DuLinklist L, int i, ElemType e)
{
DuLinklist s,p;
if (!(p=GetElemP_DuL(L,i)))
return ERROR; // 在L中確定第i個元素的位置指標p
if(!(s = (DuLinklist)malloc(sizeof(DuLNode)))) return ERROR;
s->data = e; // 構造資料為e的結點s
s->prior = p->prior; p-> prior ->next = s;
s->next = p; p->prior = s;
return OK;
} // ListInsert_DuL
刪除結點程式
Status ListDelete_DuL(DuLinklist L, int i, ElemType &e)
{
DuLinklist p;
if (!(p=GetElemP_DuL(L,i)))
return ERROR; // 在L中確定第i個元素的位置指標p
e = p->data; // 刪除的結點的值存入e
p-> prior ->next = p->next;
p->next->prior = p->prior;
free(p);
return OK;
} // ListDelete_DuL
帶頭結點的線性連結串列型別
具有實用意義的線性連結串列
typedef struct LNode // 結點型別
{
ElemType data;
LNode *next;
}*Link,*Position;
struct LinkList // 連結串列型別
{
Link head,tail; // 分別指向線性連結串列中的頭結點和最後一個結點
int len; // 指示線性連結串列中資料元素的個數
};