本節繼續介紹線性表的另外一種鏈式表示——靜態鏈表。(前面介紹的鏈表稱為 動態鏈表 )。
邏輯結構上相鄰的數據元素,存儲在指定的一塊內存空間中,數據元素只允許在這塊內存空間中隨機存放,這樣的存儲結構生成的鏈表稱為靜態鏈表。
由於各數據元素在數組申請的內存空間內隨機存放,為了體現邏輯上的相鄰,為每一個數據元素配備一個具有指針作用的整形變量,用於記錄下一元素在數組中的位置。
在數組申請的存儲空間中,各數據元素雖隨機存儲,每一個元素都記錄着下一元素在數組中的位置,通過前一個元素,可以找到下一個元素,構成了一條鏈表,這條被局限在特定內存空間的鏈表就是靜態鏈表。
實現代碼:
例如:使用靜態鏈表存儲(1,2,3,4,5),創建數組a[7]:
解決的辦法是:提前將所有未被使用的結點鏈成一個備用鏈表。需要對鏈表做插入操作時,從備用鏈表上摘下一個結點使用;刪除鏈表中的結點時,刪除的同時鏈接到備用鏈表上,以備下次使用。
圖3 備用鏈表和數據鏈表 圖3 分析:
代碼實現:
程序最終效果圖:
注:array[0]用作備用鏈表的頭結點,array[1]用作存放數據的鏈表的頭結點,所以array[0]和array[6]為備用鏈表上的結點。
實現代碼:
實現代碼:
實現代碼(在理解靜態鏈表的存儲結構的基礎上):
代碼運行效果:
實現代碼:
在該函數中,調用了一個freeArr函數,它的作用是回收被刪除結點所占用的空間,將此空間鏈接到備用鏈表中,以備下次分配使用。(自己實現的free函數)
freeArr函數實現代碼:
邏輯結構上相鄰的數據元素,存儲在指定的一塊內存空間中,數據元素只允許在這塊內存空間中隨機存放,這樣的存儲結構生成的鏈表稱為靜態鏈表。
靜態鏈表和動態鏈表的區別:靜態鏈表限制了數據元素存放的位置范圍;動態鏈表是整個內存空間。

圖1 靜態鏈表的存儲結構
靜態鏈表的構建方法
靜態鏈表使用數組這一數據類型預先申請足夠大的內存空間。由於各數據元素在數組申請的內存空間內隨機存放,為了體現邏輯上的相鄰,為每一個數據元素配備一個具有指針作用的整形變量,用於記錄下一元素在數組中的位置。
在數組申請的存儲空間中,各數據元素雖隨機存儲,每一個元素都記錄着下一元素在數組中的位置,通過前一個元素,可以找到下一個元素,構成了一條鏈表,這條被局限在特定內存空間的鏈表就是靜態鏈表。
靜態鏈表中結點的構成
靜態鏈表中每個結點既有自己的數據部分,還需要存儲下一個結點的位置,所以靜態鏈表的存儲實現使用的是結構體數組,包含兩部分: 數據域 和 游標(存放的是下一個結點在數組中的位置下標)。實現代碼:
typedef struct
{ int data;//數據域 int cur;//游標 }component;

圖2 靜態鏈表
圖2 中,鏈表頭指針指向 a[0] ,表示為第一個結點,數據域存放的是 1,通過游標確定,下一個結點的位置在 a[3] ,數據域存放的是 2 ,依次類推。若游標為 0,表示此結點為鏈表的最后一個結點。
靜態鏈表的空間重復利用
由於靜態鏈表提前申請了有限的內存空間,在使用的過程中,極有可能會出現申請的內存空間不足,需要使用之前被遺棄的內存空間。
被遺棄的意思是:之前已經使用,但是后期對該結點做了摘除操作,該內存空間中存放的是已經不用的垃圾數據。
所以,在整個過程中,需要自己動手把兩者區分開,也就是需要自己實現 malloc 和 free 兩個函數的作用。
解決的辦法是:提前將所有未被使用的結點鏈成一個備用鏈表。需要對鏈表做插入操作時,從備用鏈表上摘下一個結點使用;刪除鏈表中的結點時,刪除的同時鏈接到備用鏈表上,以備下次使用。

圖3 備用鏈表和數據鏈表
第一步:備用鏈表:(0,1)(1,2)(2,3)(3,4)(4,5)(5,6)(6,0)
數據鏈表中還沒有數據
第二步:向數據鏈表中插入一個數據,將備用鏈表上的(1,2)摘下下,提供給數據元素使用,備用鏈表的(0,1)游標直接變成2就可以了:
備用鏈表:(0,2)(2,3)(3,4)(4,5)(5,6)(6,0)
數據鏈表:(1,0)
以此類推。以上為插入結點的過程,在刪除結點的反方向操作過程中,只需要將被刪除結點從數據鏈表上摘除,並添加到備用鏈表中即可(也就是只改變相關結點的游標的值)。第三步:繼續向數據鏈表中插入一個數據,備用鏈表把(2,3)摘下來,備用鏈表中的(0,1)直接變成3就可以了:
備用鏈表:(0,3)(3,4)(4,5)(5,6)(6,0)
數據鏈表:(1,2)(2,0)
創建並初始化鏈表
建立靜態鏈表 S,存儲線性表(a,b,c,d): 創建結構體數組,例如名為 array,存儲空間足夠大; 先將 array 數組中的分量全部鏈接到備用鏈表上;(使用 reserveArr 函數實現) 從備用鏈表上申請一個分量作為鏈表 S 的頭結點,每次從備用鏈表上申請分量鏈接到 S 鏈表中,依次類推;
( mallocArr 函數用於每次向備用鏈表申請一個結點的空間,initArr 函數用於初始化靜態鏈表) 當存儲到最后一個結點時,游標設置為 0。
代碼實現:
//創建備用鏈表 void reserveArr(component *array)
{ for (int i=0; i<maxSize; i++)
{ array[i].cur = i+1;//將每個數組分量鏈接到一起 } array[maxSize - 1].cur = 0;//鏈表最后一個結點的游標值為0 }
//提取分配空間 int mallocArr(component * array)
{ //若備用鏈表非空,則返回分配的結點下標,否則返回0(當分配最后一個結點時,該結點的游標值為0) int i = array[0].cur; if (array[0].cur)
{ array[0].cur = array[i].cur; } return i; }
//初始化靜態鏈表 int initArr(component *array)
{ reserveArr(array); //鏈接備用鏈表 //從備用鏈表中拿出一個分量作為鏈表頭結點,返回的是這個分量的下標 int body = mallocArr(array); //聲明一個變量,把它當指針使,指向鏈表的最后的一個結點,因為鏈表為空,所以和頭結點重合 int tempBody = body; for (int i=1; i<5; i++)
{ int j = mallocArr(array); //從備用鏈表中拿出空閑的分量 array[tempBody].cur = j; //將申請的空線分量鏈接在鏈表的最后一個結點后面 array[j].data = 'a' + i - 1; //給新申請的分量的數據域初始化 tempBody = j; //將指向鏈表最后一個結點的指針后移 } array[tempBody].cur = 0; //新的鏈表最后一個結點的指針設置為0 return body; }

靜態鏈表中查找數據
一般情況下,訪問靜態鏈表只能通過頭結點(頭結點在數組中的位置下標是知道的),所以查找數據通過遍歷鏈表的方式實現。實現代碼:
//在以body作為頭結點的鏈表中查找數據域為elem的結點在數組中的位置 int selectElem(component * array, int body, char elem)
{ int tempBody = body; //當游標值為0時,表示鏈表結束 while (array[tempBody].cur != 0)
{ if (array[tempBody].data == elem)
{ return tempBody; } tempBody = array[tempBody].cur; }
return -1;//返回-1,表示在鏈表中沒有找到該元素 }
靜態鏈表中更改數據
更改鏈表中某結點的數據,只需要通過查找算法找到要更改結點的位置,然后直接更改該結點的數據域即可。實現代碼:
//在以body作為頭結點的鏈表中將數據域為oldElem的結點,數據域改為newElem void amendElem(component * array, int body, char oldElem, char newElem)
{ int add=selectElem(array, body, oldElem); if (add == -1)
{ printf("無更改元素"); return; } array[add].data = newElem; }
靜態鏈表中插入結點
繼續上邊的例子,插入一個結點,例如該結點的數據域為 e,插入到第 3 的位置:- 首先從備用鏈表中申請空間存儲數據元素 e;
- 由於要將 e 結點插入到第 3 的位置上,所以要找到 b 結點,將 b 結點的游標賦值給 e 結點;
- 最后將 e 結點所在位置的下標給 b 結點的游標;
實現代碼(在理解靜態鏈表的存儲結構的基礎上):
//向鏈表中插入數據,body表示鏈表的頭結點在數組中的位置,add表示插入元素的位置,a表示要插入的數據 void insertArr(component *array, int body, int add, char a)
{ int tempBody = body; //tempBody做遍歷結構體數組使用 //找到要插入位置的上一個結點在數組中的位置 for (int i=1; i<add; i++)
{ tempBody = array[tempBody].cur; } int insert = mallocArr(array); //申請空間,准備插入 array[insert].cur = array[tempBody].cur; //首先要插入結點的游標等於要插入位置的上一個結點的游標 array[insert].data = a; array[tempBody].cur = insert; //然后讓上一結點的游標等於插入結點所在數組中的位置的下標 }

靜態鏈表做刪除操作
靜態鏈表中刪除結點,要實現兩步操作:從鏈表上摘下結點后,將該結點鏈接到備用鏈表上,以備下次使用。
注:被摘除結點中的數據不需要手動刪除,待下次使用時,會被新的數據域將舊數據覆蓋點。
例如,在(a,b,c,d,e)鏈表中,刪除數據域為 ‘a’ 的結點:
實現代碼:
//刪除結點函數,a 表示被刪除結點中數據域存放的數據 void deletArr(component *array, int body, char a)
{ int tempBody = body; //找到被刪除結點的位置 while (array[tempBody].data != a)
{ tempBody = array[tempBody].cur; //當tempBody為0時,表示鏈表遍歷結束,說明鏈表中沒有存儲該數據的結點 if (tempBody == 0)
{ printf("鏈表中沒有此數據"); return; } } //運行到此,證明有該結點 int del = tempBody; tempBody = body; //找到該結點的上一個結點,做刪除操作 while (array[tempBody].cur != del)
{ tempBody = array[tempBody].cur; } //將被刪除結點的游標直接給被刪除結點的上一個結點 array[tempBody].cur = array[del].cur; freeArr(array, del); }
freeArr函數實現代碼:
void freeArr(component *array, int k)
{ array[k].cur = array[0].cur; array[0].cur = k; }
刪除數據域為 ’a’ 結點的運行效果圖:

完整實現代碼
#include <stdio.h> #define maxSize 7
typedef struct
{ char data; int cur; }component;
//將結構體數組中所有分量鏈接到備用鏈表中 void reserveArr(component *array); //初始化靜態鏈表 int initArr(component *array); //向鏈表中插入數據,body表示鏈表的頭結點在數組中的位置,add表示插入元素的位置,a表示要插入的數據 void insertArr(component * array,int body,int add,char a); //刪除鏈表中含有字符a的結點 void deletArr(component * array,int body,char a); //查找存儲有字符elem的結點在數組的位置 int selectElem(component * array,int body,char elem); //將鏈表中的字符oldElem改為newElem void amendElem(component * array,int body,char oldElem,char newElem); //輸出函數 void displayArr(component * array,int body); //自己需要實現的malloc和free函數 int mallocArr(component * array); void freeArr(component * array,int k);
int main()
{ component array[maxSize]; int body = initArr(array); printf("靜態鏈表為:\n"); displayArr(array, body); printf("在第3的位置上插入結點‘e’:\n"); insertArr(array, body, 3,'e'); displayArr(array, body); printf("刪除數據域為‘a’的結點:\n"); deletArr(array, body, 'a'); displayArr(array, body); printf("查找數據域為‘e’的結點的位置:\n"); int selectAdd = selectElem(array, body, 'e'); printf("%d\n", selectAdd); printf("將結點數據域為‘e’改為‘h’:\n"); amendElem(array, body, 'e', 'h'); displayArr(array, body); return 0; }
//創建備用鏈表 void reserveArr(component *array)
{ for (int i=0; i<maxSize; i++)
{ array[i].cur = i + 1;//將每個數組分量鏈接到一起 } array[maxSize - 1].cur = 0;//鏈表最后一個結點的游標值為0 }
//提取分配空間 int mallocArr(component * array)
{ //若備用鏈表非空,則返回分配的結點下標,否則返回0(當分配最后一個結點時,該結點的游標值為0) int i = array[0].cur; if (array[0].cur)
{ array[0].cur = array[i].cur; }
return i; }
//初始化靜態鏈表 int initArr(component *array)
{ reserveArr(array); int body = mallocArr(array); //聲明一個變量,把它當指針使,指向鏈表的最后的一個結點,因為鏈表為空,所以和頭結點重合 int tempBody = body; for (int i=1; i<5; i++)
{ int j = mallocArr(array);//從備用鏈表中拿出空閑的分量 array[tempBody].cur = j;//將申請的空線分量鏈接在鏈表的最后一個結點后面 array[j].data = 'a' + i - 1;//給新申請的分量的數據域初始化 tempBody = j;//將指向鏈表最后一個結點的指針后移 } array[tempBody].cur = 0;//新的鏈表最后一個結點的指針設置為0 return body; }
void insertArr(component *array, int body, int add, char a)
{ int tempBody = body; for (int i=1; i<add; i++)
{ tempBody = array[tempBody].cur; } int insert = mallocArr(array); array[insert].cur = array[tempBody].cur; array[insert].data = a; array[tempBody].cur = insert; }
void deletArr(component *array, int body, char a)
{ int tempBody = body; //找到被刪除結點的位置 while (array[tempBody].data != a)
{ tempBody = array[tempBody].cur; //當tempBody為0時,表示鏈表遍歷結束,說明鏈表中沒有存儲該數據的結點 if (tempBody == 0)
{ printf("鏈表中沒有此數據"); return; } } //運行到此,證明有該結點 int del = tempBody; tempBody = body; //找到該結點的上一個結點,做刪除操作 while (array[tempBody].cur != del)
{ tempBody = array[tempBody].cur; } //將被刪除結點的游標直接給被刪除結點的上一個結點 array[tempBody].cur = array[del].cur; freeArr(array, del); }
int selectElem(component * array, int body, char elem)
{ int tempBody = body; //當游標值為0時,表示鏈表結束 while (array[tempBody].cur != 0)
{ if (array[tempBody].data == elem)
{ return tempBody; } tempBody = array[tempBody].cur; } return -1;//返回-1,表示在鏈表中沒有找到該元素 }
void amendElem(component *array, int body, char oldElem, char newElem)
{ int add = selectElem(array, body, oldElem); if (add == -1)
{ printf("無更改元素"); return; } array[add].data = newElem; }
void displayArr(component *array, int body)
{ int tempBody = body; //tempBody准備做遍歷使用 while (array[tempBody].cur)
{ printf("%c,%d ", array[tempBody].data, array[tempBody].cur); tempBody = array[tempBody].cur; } printf("%c,%d\n", array[tempBody].data, array[tempBody].cur); }
void freeArr(component *array, int k)
{ array[k].cur = array[0].cur; array[0].cur = k; }
輸出結果:
靜態鏈表為: ,2 a,3 b,4 c,5 d,0 在第3的位置上插入結點‘e’: ,2 a,3 b,6 e,4 c,5 d,0 刪除數據域為‘a’的結點: ,3 b,6 e,4 查找數據域為‘e’的結點的位置: 6 將結點數據域為‘e’改為‘h’: ,3 b,6 h,4 c,5 d,0