(學習筆記 5)靜態連結串列
靜態連結串列(static linked list):用陣列來描述的連結串列,用陣列元素的下標來模擬單鏈表的指標。這種描述方法叫做遊標實現法。
遊標 | 5 | 2 | 3 | 4 | 5 | 6 | 7 | … | 1 |
---|---|---|---|---|---|---|---|---|---|
資料 | A | B | D | E | … | ||||
下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | … | 20 |
線性表的靜態連結串列儲存結構如下:
#define MAXSIZE 20
#define ElemType int
typedef struct{
ElemType data; //資料
int cur; //遊標(cursor)
} StaticLinkList[MAXSIZE];
靜態連結串列初始化:
對靜態連結串列的初始化相當於初始化陣列。
#include <stdio.h>
#define OK 1
#define ERROR 0
#define MAXSIZE 20
#define ElemType int
typedef int status;
typedef struct{
ElemType data; //資料
int cur; //遊標cursor
} StaticLinkList[MAXSIZE];
/* 初始化靜態連結串列 */
status initList(StaticLinkList space){
int i;
for(i = 0; i < MAXSIZE; i++){
space[i].data = 0; //初始化資料為0
space[i].cur = i + 1;
}
//初始化時將最後一個遊標指向0,實際有資料時指向第一個有數值的元素的下標
space[MAXSIZE - 1].cur = 0;
return OK;
}
/* 輸出靜態連結串列 */
status printList(StaticLinkList space){
int i;
for(i = 0; i < MAXSIZE; i++){
printf ("space[%d].data = %c; space[%d].cur = %d\n",i,space[i].data,i,space[i].cur);
}
return OK;
}
int main(){
//初始化靜態連結串列
StaticLinkList space;
initList(space);
return 0;
}
初始化後的靜態連結串列,如圖所示:
靜態連結串列說明:
① 靜態連結串列的第一個和最後一個元素做特殊處理,他們的data不存放資料;
② 靜態連結串列中,通常把未使用的陣列元素稱為備用連結串列;
③ 陣列的第一個元素,即下標為0的那個元素的cur就存放備用連結串列的第一個結點的下標;
④ 陣列的最後一個元素,即下標為MAXSIZE-1的cur,則存放第一個有數值的元素的下標,相當於單鏈表頭結點的作用;
⑤ 在有值的結點中,最後一個結點的cur儲存0,即指向陣列的第一個元素
靜態連結串列賦值:
比如,建立一個data分別為A、B、D、E的靜態連結串列。最終需要實現如下效果:
遊標 | 5 | 2 | 3 | 4 | 5 | 6 | 7 | … | 1 |
---|---|---|---|---|---|---|---|---|---|
資料 | A | B | D | E | … | ||||
下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | … | 20 |
程式碼實現如下:
/**
* 建立一個有值靜態連結串列
*/
status createList(StaticLinkList space,char data[4]){
int i,len;
len = strlen(data);
//第一個結點(i = 0)不存放資料,所以迴圈從i = 1開始
for(i = 1; i <= len; i++){
if(i == 1){
//最後一個結點的cur存放第一個有值的結點的下標
space[MAXSIZE - 1].cur = i;
}
space[i].data = data[i-1]; //給i對應的結點賦值
if(i == len){
//第一個結點(i = 0)的cur存放備用連結串列的第一個結點的下標,即i的cur
space[0].cur = space[i].cur;
//i為最後一個有值的節點,其cur中儲存第一個結點的下標,即0
space[i].cur = 0;
}
}
return OK;
}
/* 輸出靜態連結串列 */
status printList(StaticLinkList space){
int i;
for(i = 0; i < MAXSIZE; i++){
printf(" space[%d].data = %c; space[%d].cur = %d\n",i,space[i].data,i,space[i].cur);
}
return OK;
}
/* 只輸出賦值部分的靜態連結串列(不再包含未用到的備用連結串列)*/
status printList2(StaticLinkList space){
int i,k;
//靜態連結串列的最後一個結點的cur中,儲存的是第一個有值的節點的下標
k = space[MAXSIZE-1].cur;
for(i = 1; i < MAXSIZE; i++){
//只輸出有值的靜態連結串列,備用連結串列不再輸出
//因為靜態連結串列中最後一個賦值的結點的cur儲存的是0,所以k == 0 可以作為結束標識
if(k == 0) break;
printf(" space[%d].data = %c; space[%d].cur = %d\n",k,space[k].data,i,space[k].cur);
k = space[k].cur; //移動到下一個結點
}
return OK;
}
int main(){
//定義字串陣列
char data[] = "ABDE";
//初始化靜態連結串列
StaticLinkList space;
initList(space);
//printList(space);
//為靜態連結串列賦值,生成具有現實意義的靜態連結串列
createList(space,data);
printList(space); //輸出整個靜態連結串列
printf("==================\n");
printList2(space); //只輸出賦值部分的靜態連結串列
return 0;
}
輸出截圖如下:
靜態連結串列的插入:
思考:如何用靜態連結串列墨跡動態連結串列結構的儲存空間分配呢?也就是需要的時候申請,不需要的時候能立即釋放呢?
通過前面所學我們知道,在動態連結串列中,結點的申請和釋放分別借用c語言的malloc()和free()兩個函式來實現的。在靜態連結串列中,操作的是陣列,不存在像動態連結串列的節點申請和釋放的問題,為了明確陣列中哪些分量未被使用,解決的辦法是將所有未被使用過的及被刪除的用遊標鏈成一個備用連結串列。
每當進行插入操作時,便可以從備用連結串列中取得第一個結點作為待插入的新結點。假設要在B後面插入C,圖例如下:
演算法實現:
首先需要獲得空閒分量的下標,即備用連結串列的第一個結點的下標;
修改第一個結點和賦值的最後一個結點的cur儲存的備用連結串列的下標,修改為備用連結串列第二個節點的下標,即備用連結串列的第一個結點的cur儲存的值;
將待插入資料賦值給備用連結串列的第一個結點的下標,將其cur指向待插入位置的下標;
修改待插入位置的前一個元素的cur為待插入元素的下標。
程式碼實現如下:
/**
* 向靜態連結串列中下標i位置插入元素e
* @param StaticLinkList space 靜態連結串列
* @param int i 待插入位置的下標,靜態連結串列的下標從0開始
* @param ElemType e 待插入的值
*/
status insertList(StaticLinkList space,int i, ElemType e){
int l,j,precur,k;
if(i < 1 || i >= space[0].cur){
return ERROR;
}
//首先需要獲得空閒分量的下標,即備用連結串列的第一個結點的下標
l = space[0].cur;
//給備用連結串列中的第一個結點的data賦值
space[l].data = e;
//因為備用連結串列的第一個結點被使用了,所以需要修改space[0].cur儲存的值
//將第一個節點cur(space[0].cur)儲存為備用連結串列的第二個節點的下標,即備用連結串列第一個結點的cur中儲存的值
space[0].cur = space[l].cur;
//k為臨時存放當前節點下標的引數,初始值儲存的是第一個有值的節點下標
k = space[MAXSIZE - 1].cur;
for(j = 1; j < MAXSIZE; j++){
//儲存待插入結點的前一個結點下標
//因為其指向的下一個結點改變了,此處先記錄下來
if(space[k].cur == i){
precur = k;
break;
}
//移動當前節點
k = space[k].cur;
}
//待插入結點的前一個結點的cur應該指向待插入的結點下標(i為插入結點的位置)
space[precur].cur = l;
//將待插入結點的cur(space[k].cur)指向i位置的節點下標(相當於i位置的結點要後移一位)
space[l].cur = i;
return OK;
}
輸出截圖如下:
靜態連結串列的刪除:
(1)刪除連結串列中第i個數據元素
例如,刪除第3個元素:
程式碼如下:
/**
* 刪除連結串列中第i個數據元素
*/
status deleteList(StaticLinkList space, int i){
int j,k;
if(i < 1 || i >= space[0].cur){
return ERROR;
}
//k為臨時存放當前節點下標的引數,初始值儲存的是第一個有值的節點下標
k = MAXSIZE - 1;
for(j = 1; j <= i - 1; j++){
k = space[k].cur; //移動當前節點
if(j == i-1){
space[space[k].cur].data = 0;
}
}
//先儲存第i個元素結點的下標
j = space[k].cur;
//將當前結點的前一個結點的遊標指向當前節點的下一個結點(當前節點即為將要刪除的節點)
space[k].cur = space[j].cur;
//當前節點的遊標儲存原備用連結串列的第一個下標,即將原備用連結串列的第一個結點變為備用第二結點
space[j].cur = space[0].cur;
//將當前節點置為備用連結串列的第一個結點
space[0].cur = j;
return OK;
}
輸出結果截圖如下:
(2)刪除結點值等於data的結點
例如,刪除結點值等於B的結點,程式碼如下:
/**
* 刪除連結串列中結點data等於e的元素
*/
status deleteListByData(StaticLinkList space, ElemType e){
int j,i,k,precur;
//k為臨時存放當前節點下標的引數,初始值儲存的是第一個有值的節點下標
k = MAXSIZE - 1;
for(j = 1; j < MAXSIZE - 1; j++){
precur = k; //記錄上一個節點位置
k = space[k].cur; //移動當前節點
if(space[k].data == e){ //找到了要刪除的結點
space[k].data = 0;
i = k;
//將當前結點的前一個結點的遊標指向當前節點的下一個結點(當前節點即為將要刪除的節點)
space[precur].cur = space[space[precur].cur].cur;
break;
}
}
//當前節點的遊標儲存原備用連結串列的第一個下標,即將原備用連結串列的第一個結點變為備用第二結點
space[i].cur = space[0].cur;
//將當前節點置為備用連結串列的第一個結點
space[0].cur = i;
return OK;
}
輸出截圖如下:
靜態連結串列優缺點總結:
優點:
在插入和刪除操作時,只需要修改遊標,不需要移動元素,從而改進了在順序儲存結構中的插入刪除操作需要移動大量元素的缺點。
缺點:
沒有解決連續儲存分配(陣列)帶來的表長難以確定的問題。
失去了順序儲存結構隨機存取的特性。