資料結構-靜態連結串列及其插入刪除操作
什麼是靜態連結串列
我們平常提及的連結串列一般指的是動態連結串列,是使用指標將一個一個的結點連起來,除了動態連結串列之外,還有靜態連結串列,這種連結串列用陣列來描述,主要為了解決沒有指標或者不用指標的情況下具備連結串列插入刪除操作便捷的特性。
靜態連結串列中有一些專屬的概念,先貼上圖:
這就是一個靜態連結串列,首先他是一個數組(長度為8),陣列的型別顯然要根據需要定義,在上圖中的型別為:
typedef struct
{
ElemType data;
int cur;
} Component,StaticLinkList[MAXSIZE];
一般我們定義一個有兩個資料區域的結構體陣列:
一個區域(data
另一個區域(cur)用來存放“指向”的下一個資料區域陣列的下標(這裡的指向並不指的是指標),cur也被稱之為遊標,它裡面存放的是陣列的下標(先不管存放的規則如何,存放的就是陣列的下標)
陣列中的元素(除了第一個和最後一個)按照有沒有被使用分類的,可以分為兩類(就像上面的紅色和綠色),這兩類元素可以分別構成鏈,我們可以把沒有使用的那個鏈叫做被備用鏈。
陣列中的第一個元素(下標0)與最後一個元素(下標n-1)是不存放資料的,第一個元素的cur存放備用鏈的第一個元素的下標,最後一個元素的cur存放使用的連結串列的第一個元素的下標。比如上圖中最後一個元素中cur放的是1,1是第一個紅色框的下標。
至此,靜態連結串列就構建完了。
靜態連結串列的插入操作
從上面的圖可以看到,其實陣列的最後一個元素的cur存的是一般都是1,因為在使用元素構建連結串列時從第二個元素開始順序插入,而插入的位置在哪,其實是由cur決定的,都不是順序儲存中由位置決定。
所以,假設已經有了如上圖所示的連結串列,現在想在第i個元素前插入一個元素,可以這樣來做:
(1)從備用鏈裡面找出來第一個元素的下標j,讓備用鏈減一;
(2)將要插入的值給j的data;
(3)i是相對於使用連結串列的長度來說的,所以要判斷使用的連結串列的長度,判斷i是否在範圍內;
(4)找到i的前一個元素下標k;
(5)k的cur先變成j的cur,因為k插入到j後面,j以前的後續要有k連結;
(6)k的cur賦值為j,由於j是一個下標,j就是cur。
(5)(6)的思路和動態連結串列的插入很像,都是要先解決後面的東西,在插前面,如果(5)(6)對調,連結串列斷開後,k的cur原來的數被j覆蓋,連結串列將無法重新連線。
#define MAXSIZE 1000
typedef int Status;
typedef char ElemType;
//定義陣列的型別
typedef struct
{
ElemType data;
int cur;
} Component,StaticLinkList[MAXSIZE];
//在備用表中找到並返回第一個元素的下標,並改變陣列[0]的cur
int Malloc_SSL(StaticLinkList space)
{
int i = space[0].cur;
if (space[0]. cur)
space[0]. cur = space[i].cur;
return i;
}
//獲取使用連結串列的長度
int ListLength(StaticLinkList L)
{
int j=0;
int i=L[MAXSIZE-1].cur;
while(i)
{
i=L[i].cur;
j++;
}
return j;
}
//插入操作
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
int j, k, l;
k = MAXSIZE - 1;
//判斷是否超範圍
if (i < 1 || i > ListLength(L) + 1)
return 0;
//返回備用表第一個元素的下標
j = Malloc_SSL(L);
if (j)
{
L[j].data = E;
//找到i前面的元素下標k
for(l = 1; l <= i - 1; l++)
k = L[k].cur;
//j繼承k的cur
L[j].cur = L[k].cur;
//給k
L[k].cur = j;
return 1;
}
return 0;
}
需要說明的是for迴圈的內容,第一次看到這個for迴圈時可能覺得很奇怪,用l在控制迴圈,但是下面的內容沒有變數l的參與,這是因為for只控制迴圈的次數就好了,k從陣列的最後一位開始,而陣列[MAXSIZE - 1]的cur中存放的就是是用連結串列第一個元素的下標,第一個元素的cur中放的又是第二個元素的下標,每一次k都會變成連結串列中下一個元素的下標。
靜態連結串列的刪除操作
刪除操作是一樣的,在插入中,插入一個元素影響了使用鏈和備用鏈。那麼刪除一個元素的話也會同時影響這兩個鏈。
首先考慮備用鏈,由於原連結串列中一個元素被刪除了,在上圖中是下標3的元素,那麼原備用鏈中第一個元素就不再是下標5了,而應該是3,也就是說再有插入操作的時候優先插入的位置是3。那麼我們就可以寫出一個和Malloc_SSL函式功能相反的函式—Free_SSL。
void Free_SSL(StaticLinkList space, int j)
{
space[j].cur = space[0].cur;
space[0].cur = j;
}
看下這個函式,再看下之前插入程式碼裡面的這兩行:
//j繼承k的cur
L[j].cur = L[k].cur;
//給k
L[k].cur = j;
你會發現這是一毛一樣的操作,這就是在下標0與備用鏈第一個元素之間插了一個j,而之前的程式碼是在下標k與k後面的元素之間插了一個j。
其次就是原連結串列的變化,要刪除連結串列中i位置的元素,同樣要找到這個位置的陣列的前一個數組的下標k,而k位置的元素的cur存放著要刪除的元素的下標j,j的cur裡面又放著下一個元素的下標,我們需要做的就是把這個下標給k的cur,這樣一來,j對應的元素就從原連結串列中刪除了。
Status ListDelete(StaticLinkList L, int i)
{
int j, k;
if (i < 1 || i > ListLength(L))
return ERROR;
k = MAXSIZE - 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;
}
拿上面的圖解釋一下這個過程,要刪除下標為3的元素,通過for迴圈找到它前面的元素,此時(下面的“=”是數學裡面相等的關係,不是賦值!!):
k=2;
L[2].cur=3=j;
L[j].cur=L[3].cur=4;
L[2].cur=4;
執行 Free_SSL(L, 3);
L[3].cur=5;
L[0].cur=3;