資料結構之線性表(二)
資料結構之線性表一主要講的是線性表的順序儲存結構和鏈式儲存結構的實現和程式碼。這次我們來討論下靜態連結串列,迴圈連結串列和雙向連結串列。
靜態連結串列
我們讓陣列每個元素都是由兩個資料域組成:data和cur。資料域data用來儲存資料元素,cur相當於我們連結串列中的next指標,存放該資料元素的後繼在陣列中的下標。我們把這種陣列描述的連結串列叫做靜態連結串列。
基本結構
/*線性表的靜態連結串列儲存結構*/ #define MAXSIZE 1000 typedef struct { ElemType data; int cur; /*遊標(Cursor),為0時表示無指向*/ }Component,StaticLinkList[MAXSIZE];
在靜態連結串列中,我們將陣列的第一個和最後一個元素作為特殊元素處理,不存放資料。此外,將未被使用的陣列元素稱為備用連結串列。而陣列的第一個元素,即下標為0的元素的cur就存放備用連結串列的第一個結點的下標。而陣列的最後一個元素的cur則存放第一個有數值元素的下標,相當於連結串列的頭結點作用。
初始化
/*將一維陣列space中各分量鏈成一備用連結串列*/ /*space[0].cur為頭指標*/ Status InitList(StaticLinkList space) int i; for(i=0;i<MAXSIZE-1;i++) space[i].cur = i+1; space[MAXSIZE-1].cur = 0; /*目前靜態連結串列為空,最後一個元素的cur為0*/ return OK;
靜態連結串列的記憶體分配
靜態連結串列中存放的是陣列,不存在動態連結串列中結點的申請與釋放函式malloc()和free()。
為了辨明陣列中哪些分量未被使用,解決的辦法是將所有未被使用過的及已被刪除的分量用遊標鏈成一個備用的連結串列。每當進行插入時,便可以從備用連結串列上取得第一個結點作為待插入的新結點。
/*若備用連結串列非空,返回分配的結點的下標,否則返回0*/ int Malloc_SLL(StaticLinkList space) { int i = space[0].cur; /*返回備用連結串列的第一個結點的下標*/ if(space[0].cur) space[0].cur = space[i].cur; /*由於使用了一個元素,我們將它的後繼連結元素作為備用連結串列的頭*/ return i; }
靜態連結串列的插入
實現:在L中第i個元素之前插入新元素e
現在我們需要在‘乙’和‘丁’之間,插入一個新元素‘丙’。怎麼實現呢? 我們只需要將‘丙’放入備用連結串列的第一個空位置,也就是下標為7的位置。 另外將‘乙’的遊標改為7,‘丙’的遊標改為3。 這樣實現了不移動元素,完成了插入的動作。
/*在L中第i個元素之前插入新元素e*/
Status ListInsert(StaticLinkList L, int i, ElemType e) {
int j, k ,l; k = MAX_SIZE -1; /*獲取陣列最後一個位置下標*/
if(i<1 || i>ListLength(L)+1)
return ERROR;
j = Malloc_SSL(L); /*獲取備用連結串列第一個位置的下標*/
if(j) {
L[j].data = e; /*將數值賦給資料域data*/
for(l=1;l<=i-1;l++)
k = L[k].cur /*獲取第i個元素之前位置的下標*/
L[j].cur = L[k].cur;
L[k].cur = j; /*cur值之間的重新連結*/
return OK;
}
return ERROR;
}
靜態連結串列的刪除
與記憶體分配相對應的有記憶體回收,即將空閒結點回收到備用連結串列中。
/*將下標為k的空閒結點回收到備用連結串列*/
void Free_SSL(StaticLinkList space, int k) {
space[k].cur=space[0].cur /*把原來第一個元素遊標值賦給要刪除的分量
cur*/ space[0].cur = k; /*把要刪除的下標值賦給第一個預算的cur*/ }
具體刪除操作:
/*刪除L中第i個數據元素e*/
Status ListDelete(StatusLinkLiST L,int i) {
int j,k;
if(i<1 || i>ListLength(L))
return ERROR;
k = MAX_SIZE - 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;
}
j=L[999].cur=1; L[999].cur=L[1].cur=2. 這其實就是告訴計算機‘甲’已經離開了,‘乙’才是第一個元素。
靜態連結串列優缺點:
優點: * 在插入和刪除操作時只需修改遊標,不需要移動元素。從而改進了在順序儲存結構中的插入和刪除;操作需要移動大量元素的缺點。
缺點: * 沒有解決連續儲存分配帶來的表長難度以確定的問題; * 失去了順序儲存結構隨機存取的特性。
迴圈連結串列
將單鏈表中終端結點的指標端有空指標改為指向頭結點,就是單鏈表形成一個環,這種頭尾相接的單鏈表稱為單迴圈列表,簡稱迴圈列表。迴圈列表解決了從連結串列中任意個結點訪問連結串列全部結點的問題。
上圖即為非空迴圈連結串列,迴圈連結串列和單鏈表的主要差異在於迴圈的判斷上,原來判斷p-->next是否為空,現在則是p-->next不等於頭結點,則迴圈未結束。
在單鏈表中如果我們有了頭結點,我們可以用O(1)的時間訪問第一個結點,而需要O(n)的時間才能訪問到最後一個結點,因為我們需要將整個單鏈表全部掃描一遍。我們通過改造迴圈連結串列可以實現用O(1)的時間來查詢頭結點和尾結點,我們採用尾指標來表示迴圈連結串列,如下圖:
上圖可以看出,終端結點用尾指標rear來表示,開始結點可以使用rear-->next-->next來表示,這樣時間複雜度都是O(1)
假設將兩個連結串列合併成一個的時候,有了尾指標就很方便,比如下面兩個迴圈連結串列,它們的尾指標分別是rearA和rearB
將它們合併,只需要以下操作,
p=rearA->next ;//儲存A表頭結點,即①
rearA->next=rearB->next->next //將本是指向B表第一個結點的指標賦值給rearA->next,即②
rearB->next=p;//將原A表的頭結點賦值給rearB->next,即③
free(p)//釋放p
雙向連結串列
在單鏈表中我們訪問下一個結點的時間複雜度為O(1),可是查詢上一個結點的話,最壞的時間複雜度是O(n),因為我們有可能需要從頭遍歷查詢,雙向連結串列是在單鏈表的每個結點上,在設定一個指向其前驅結點的指標域,所以雙向連結串列結點都有兩個指標域,一個指向前驅,一個指向後繼。
雙向連結串列帶頭結點的空連結串列結構圖:
非空的帶頭結點雙向連結串列結構:
對於一個雙向連結串列,他前驅的後繼和他後繼的前驅都是他自己,即p->next->prior = p = p->prior->next
雙向連結串列比單向連結串列多了可以反向查詢等資料結構,但是在插入和刪除時需要更改的是兩個指標變數,
插入操作:假設儲存元素e的結點為s,實現s插入結點p和結點p->next之間需要以下操作
s->prior = p;//將p賦值給s的前驅,圖①
s->next=p->next //把p->next賦值給s的後繼 ,圖②
p->next->prior =s //把s賦值給p-next的前驅,圖③
p-next =s //把s賦值給p的後繼,圖④
由於第二步和第三步都用到了p->next,如果第四步先執行,則p-next會提前變為s,這樣就錯誤了,所以我們一般插入的順序:插入結點的前驅和後繼---搞定後結點的前驅----前結點的後繼
刪除操作:刪除結點p
p->prior-next = p->next; //把p->next賦值p->prior的後繼,圖①
p->next->prior = p->prior; //把p->prior賦值給p->next的前驅,圖②
線性表結構圖