上次介紹了單鏈表的實現。如果你認真的在計算機上敲出書上的算法並將其實現,你一定會有所收穫的,對指針的使用也會熟練很多。
還是老規矩:
程序在碼雲上可以下載。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git
我們不得不說指針確實是個好東西,指針的存在大大增強了程序的靈活性,只要我們願意,指針可以指向任何我們想要指向的已知的數據類型。但是早期的程序設計語言有些是不支持指針的(比如Basic和Fortran),那是不是在這些語言中就沒法實現鏈表了呢?誰說用鏈表就必須要指針,指針僅僅是起一個存儲地址的作用,那我們爲什麼不能在順序存儲結構中存放地址呢,只不過這個地址不用指針,只是用下標或位序(就是個int型整數)表示,雖然實際的存儲用的是順序存儲,但是這樣的結構卻具備了鏈表的功能,這就達到了在沒有指針概念的程序設計語言中實現鏈表的目的。
既然已經有辦法在一段順序的存儲空間上實現鏈表數據結構,那具體該怎麼做呢?
首先我們要明確,由於這些程序設計語言不支持指針概念,我們只能事先申請一段足夠長的數組空間,使用它來存放和管理我們的鏈表,由於這段存儲空間的大小在編譯時就可以確定,而且數組的大小無法在程序運行過程中動態分配,所以我們把這樣的結構稱之爲靜態鏈表。
其次,由於我們的存儲空間大小在編譯的時候就已經確定並且無法在運行過程中改變,所以我們只能手工去管理這段內存空間,我們需要手工寫代碼實現內存空間的分配、釋放和初始化。這裏面就包含了很多的編程思想和編程技巧。
靜態鏈表和單鏈表的不同之處不僅是存儲結構上的差異,更重要的是:我們的靜態鏈表可以在一段數組空間上同時存儲和管理多個鏈表,但是單鏈表卻無法從已有的存儲空間上分出一段空間來存放第二個鏈表。
本次的程序中使用了大量的編程技巧。
1.手工寫代碼在一段連續的存儲空間上完成內存的動態分配和釋放以及存儲單元的初始化,這本身就需要一些技巧。
2.要在一段數組上存儲和管理多個鏈表,要保證鏈表各項操作的可用性和正確性,還要防止多個鏈表的操作相互影響。
我們想要實現內存的分配和回收就必須要引入備用鏈表,此時我們在一個數組中不僅要在多個數據鏈表中存儲和維護我們的數據,還要管理好備用鏈表,我們的內存分配全靠它,他後面鏈接着很多的空閒結點,每次分配內存就要從這個備用鏈表上摘下一個節點,釋放內存則要將回收的結點掛回到這個鏈表上。所以我們要在一個數組上搞出多個鏈表,有點像操作系統的存儲器管理,學過操作系統的童鞋應該聽過“空閒分區鏈”這個概念吧。
總而言之,上面那段話提到了兩個重要概念:
備用鏈表(任何時候只有一個):就像一個管家一樣管理大數組中暫時用不到的空閒結點。備用鏈表的的頭結點約定就在數組的0號存儲單元。
數據鏈表(只要還有足夠的存儲空間,理論上可以分配任意多個數據鏈表):存放我們的數據,所有數據結點都是從備用鏈表摘下來投入使用的,用完也必須被回收,掛回到備用鏈表。
接下來就要看看程序實現了:
//*******************************************引入頭文件*********************************************
#include <stdio.h> //使用了標準庫函數
//******************************************自定義符號常量*******************************************
#define DestoryList ClearList //DestoryList()和ClearList()的操作是一樣的
#define OVERFLOW -2 //內存溢出錯誤常量
#define ILLEGAL -1 //非法操作錯誤常量
#define OK 1 //表示操作正確的常量
#define ERROR 0 //表示操作錯誤的常量
#define TRUE 1 //表示邏輯正確的常量
#define FALSE 0 //表示邏輯錯誤的常量
//******************************************自定義數據類型********************************************
typedef char ElemType; //元素類型
typedef int Status; //狀態參量類型
//----------------線性表的靜態單鏈表存儲結構--------------------
#define MAXSIZE 1000 //鏈表的最大長度
typedef struct{
ElemType data; //數據域
int cur; //遊標,作用相當於指針域
}component, SLinkList[MAXSIZE];
//***************************************線性靜態單鏈表的主要操作******************************************
/*
函數:Malloc
參數:SLinkList L 靜態鏈表首地址
返回值:新開闢結點的座標
作用:(申請結點空間)若備用空間鏈表非空,從備用鏈表取出一個空閒結點
並返回該結點下標,否則返回0
*/
int Malloc(SLinkList L){
//L[0].cur記錄了備用鏈表第一個空閒結點在數組中的位置
int i = L[0].cur;
//cur的值爲0相當於指針域爲NULL
//cur的值爲0表示該節點後面沒有後繼,該節點是鏈表中的最後一個結點
//i的值不是0表示備用鏈表中還有空閒結點可以使用
if(i) { //if(i) <=> if(i != 0)
//備用鏈表的頭結點指向原備用鏈表的第二個結點
//i(也就是L[0].cur)指示了第一個空閒結點的位置,
//所以L[i].cur指示了第二個空閒結點的位置。
//L[0]是備用鏈表的頭結點,所以L[0].cur = L[i].cur;這句代碼
//就是從備用鏈表把第一個空閒結點取走,然後把第二個空閒結點掛回到備用鏈表上
L[0].cur = L[i].cur;
}//if
//返回新開闢結點的座標
return i;
}//Malloc
/*
函數:InitList
參數:SLinkList L 靜態鏈表首地址
返回值:空表在數組中的位序
作用:構造一個空鏈表
*/
int InitList(SLinkList L){
//注意:此時一個數組中存儲了兩個鏈表:靜態鏈表數據存儲區域和備用鏈表
//靜態鏈表的頭結點在數組中的位置就是i,但是i不是0,因爲0號單元已經規定
//就是備用鏈表的頭結點。所以L[i].cur是靜態鏈表首元結點。
//而備用鏈表的頭結點是L[0],首元結點在數組中的位置是L[0].cur。
//由於一個數組中同時存儲了兩個鏈表,所以要區分開靜態鏈表數據存儲區域和
//備用鏈表,尤其要區分兩者的頭結點和首元結點的位置上的不同。
//而且要注意:由於靜態鏈表數據存儲鏈表的頭結點是從備用鏈表上取出來的,位置不固定
//所以在調用的時候要用變量保存頭結點在數組中所在的位置,否則後續操作將無法進行
int i;
//i存儲了新開闢結點的座標
i = Malloc(L);
//由於靜態鏈表目前剛剛初始化,除了頭結點一個數據節點都沒有,所以要
//把靜態鏈表頭結點的指針域【也就是cur】設置爲0,意思是後面沒有後繼結點
L[i].cur = 0;
//新開闢的第一個結點就是靜態鏈表的頭結點,頭結點的位置爲i
return i;
}//InitList
/*
函數:InitSpace
參數:SLinkList L 靜態鏈表首地址
返回值:無
作用:(初始化備用空間)將一維數組L中各分量鏈成一個備用鏈表,
L[0].cur爲頭指針,"0"代表空指針
這個函數是用來初始化備用鏈表的,不是用來初始化靜態鏈表的數據存儲區域
*/
void InitSpace(SLinkList L) {
//此時靜態鏈表還未投入使用,所以整個數組都是備用鏈表的空間
for(int i = 0; i < MAXSIZE - 1; ++i) {
//將第i+1個元素的下標(相對地址)存在第i個元素的cur中
L[i].cur = i + 1;
}//for
//最後一個元素的cur存放0,表示空指針,即該位置爲備用鏈表表尾
L[MAXSIZE - 1].cur = 0;
}//InitSpace
/*
函數:ClearList
參數:SLinkList L 靜態鏈表首地址
int n 存儲數據鏈表表頭結點在數組中的位序
返回值:無
作用:將線性表L置成空表,由於靜態鏈表的大小在編譯的時候就已經確定,
所以靜態鏈表的內存不是動態分配的,也就不存在銷燬這樣的操作了。
由於清空操作造成的效果和銷燬差不多,兩者都可以清空已經存在的數據,
只不過清空不可以釋放靜態鏈表佔用的內存空間,所以可以把清空操作
當成是靜態鏈表的銷燬操作。
*/
void ClearList(SLinkList L, int n){
int i = 0, j = 0, k = 0;
//先回收靜態鏈表數據存儲鏈表的頭結點,因爲這個節點也是從備用鏈表
//上面摘下來的,所以這個節點要先收回來。
//i指示存儲數據鏈表首元結點的位置,由於是要回收頭結點,所以要
//保存首元結點在數組中的位置,否則頭結點被回收之後將無法繼續
//回收後面的存儲數據的結點。
i = L[n].cur;
//L[n].cur指示了數據鏈表頭結點後面的首元結點在數組中的位置
//想要回收頭結點就要把頭結點和首元結點的連接切斷,所以要
//將L[n].cur置爲0,使頭結點從數據鏈表上脫離下來。
L[n].cur = 0;
//k是備用鏈表第一個空閒結點在數組中的位置,k是起臨時保存作用的
//回收鏈表之後還要把k指示的結點及其後面的鏈條掛回到備用鏈表的後面
//如果不保存k就會造成備用鏈表原有空閒結點丟失,在使用過程中可用空間無故變少。
k = L[0].cur;
//回收了數據鏈表頭結點之後就要開始回收數據鏈表的首元結點以及後面的全部結點了。
//把數據鏈表的所有未被回收的結點連接到備用鏈表表頭,這些節點將會被回收
L[0].cur = i;
//從數據鏈表的首元結點開始,向後回收每一個結點,直到數據鏈表表尾。
//如果某個結點的cur值爲0,表示該結點沒有後繼,即該結點是鏈表的最後一個結點
//所以i != 0的意思是最後一個結點還沒回收完。因爲只有鏈表最後一個結點的cur爲0。
while(i){ //while(i) <=> while(i != 0)
//j記錄了被回收的結點在數組中的位置,最後一趟循環完成後,
//j指示的位置剛好是數據鏈表的尾元結點
j = i;
//i指向下一個元素
i = L[i].cur;
}//while
//備用鏈表在結點回收前後面就有一條空閒結點組成的鏈條,由於我們在回收數據鏈表
//結點的空間時已經重置了L[0].cur的值,所以舊的備用鏈表的空閒結點實際上已經和
//備用鏈表的頭結點L[0]斷開了連接,所以原先在備用鏈表中的頭結點位置已經不被
//備用鏈表頭結點記錄了,如果我們不把它保存起來就會丟掉原有的空閒結點,導致
//靜態鏈表的空閒結點在使用過程中越變越少,一部分空閒結點不受備用鏈表頭結點管制
//且這些節點沒法回收。所以備用鏈表的原來的首元結點在數組中的位置在回收前需要
//先保存在k變量中,只要找到數組中第k個位置,就可以順着這個空閒結點找到後面所有的原先
//已經在備用鏈表中的空閒結點。現在我們要做的就是把新回收的空閒結點和原有的空閒
//結點合併到一個鏈中,統一被備用鏈表頭結點的管理。具體做法就是直接把k指示的
//原有的空閒結點鏈條中的首元結點接到新的備用鏈表尾部,使它們連接在一起就可以了。
L[j].cur = k;
}//ClearList
/*
函數:Free
參數:SLinkList L 靜態鏈表首地址
int k 被回收結點的在數組中的位置
返回值:無
作用:將下標爲k的空閒結點回收到備用鏈表
*/
void Free(SLinkList L, int k){
//L[0].cur存儲了備用鏈表第一個可用結點在數組中的位置
//L[k].cur = L[0].cur; 這行代碼就是把原來的空閒結點組成的鏈條
//掛到這個將要回收的結點後面
L[k].cur = L[0].cur;
//待回收結點k就被加入到了備用鏈表中,成爲了空閒結點鏈條中的第一個結點。
//(相當於在備用鏈表頭部插入一個結點)。
L[0].cur = k;
}//Free
/*
函數:LocateElem
參數:SLinkList L 靜態鏈表首地址
int n 靜態鏈表頭結點在數組中的位置
ElemType e 查找值爲e的元素
返回值:如果找到第一個值爲e的元素,返回查找到的元素在L中的位序,否則返回0
作用:在靜態單鏈線性表L中查找第一個值爲e的元素
*/
int LocateElem(SLinkList L, int n, ElemType e){
//i指向表中第一個結點
int i = L[n].cur;
//在表中順鏈查找(若是字符串需要用strcmp()函數比較)
//while(i && L[i].data != e) <=> while(i != 0 && L[i].data != e)
while(i && L[i].data != e) {
//i指向下一個結點
i = L[i].cur;
}//while
//如果找到了值爲e的結點,循環會提前終止,此時i存儲的就是值爲e的結點在數組中的位置
//如果沒有找到,循環正常結束時i保存了尾元結點的cur,而尾元結點的cur值是0,
//注:當某個節點cur的值爲0時表示該結點是鏈表的最後一個結點,故後面沒有後繼。
return i;
}//LocateElem
/*
函數:Print
參數:ElemType e 被訪問的元素
返回值:無
作用:訪問元素e的函數,通過修改該函數可以修改元素訪問方式,
該函數使用時需要配合遍歷函數一起使用。
*/
void Print(ElemType e){
//元素訪問的方式是打印輸出
printf(" %c ", e);
}//Print
/*
函數:ListTraverse
參數:SLinkList L 靜態鏈表L
int n 靜態鏈表數據鏈表頭結點在數組中的位置
Status(* visit)(ElemType) 函數指針,指向元素訪問函數。
返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR
作用:調用元素訪問函數完成靜態鏈表的遍歷,所謂遍歷就是逐一訪問線性表中的每個元素。
*/
void ListTraverse(SLinkList L, int n, void(* Visit)(ElemType)){
//i存儲了靜態鏈表數據鏈表的首元結點在數組中的位置
int i = L[n].cur;
//i != 0表示沒到靜態鏈表表尾
while(i){ //while(i) <=> while(i != 0)
//調用Visit()函數訪問元素
Visit(L[i].data);
//i指向下一個元素
i = L[i].cur;
}//while
//輸出換行,使程序在控制檯上的輸出清楚美觀
printf("\n");
}//ListTraverse
/*
函數:ListEmpty
參數:SLinkList L 靜態鏈表L
int n 靜態鏈表數據鏈表頭結點在數組中的位置
返回值:若L是空表,返回TRUE,否則返回FALSE
作用:判斷靜態鏈表L是否是空表
*/
Status ListEmpty(SLinkList L, int n) {
//n指示了靜態鏈表數據鏈表頭結點在數組中的位置,
//所以L[n].cur指示了靜態鏈表數據鏈表首元結點在數組中的位置
//如果L[n].cur的值爲0表示靜態鏈表數據鏈表頭結點後面沒有後繼。
//此時靜態鏈表就是隻有頭結點沒有數據節點的空表。
if(L[n].cur == 0) { //空表
return TRUE;
}//if
else {
return FALSE;
}//else
}//ListEmpty
/*
函數:ListLength
參數:SLinkList L 靜態鏈表L
int n 靜態鏈表數據鏈表頭結點在數組中的位置
返回值:若L是空表,返回TRUE,否則返回FALSE
作用:獲取靜態鏈表的長度
*/
int ListLength(SLinkList L, int n){
//i存儲了靜態鏈表數據鏈表的首元結點在數組中的位置
//j是記錄靜態鏈表長度的計數器
int j = 0, i = L[n].cur;
//靜態鏈表不像單鏈表和順序表,0號結點的cur存的是備用鏈表首元結點
//在數組中的位置,沒有存儲表長。所以只能遍歷整個鏈表計算表長。
//沒到靜態鏈表尾
while(i){ //while(i) <=> while(i != 0)
//i存儲了下一結點在數組中的位置
i = L[i].cur;
//計數器+1
j++;
}//while
//計數器在循環後的值就是數據節點的個數,也就是表長
return j;
}//ListLength
/*
函數:GetElem
參數:SLinkList L 靜態鏈表L
int n 靜態鏈表數據鏈表頭結點在數組中的位置
int i 得到第i個位置的元素的值
ElemType &e 帶回第i個位置的元素e
返回值:狀態碼,OK表示操作成功,ERROR表示操作失敗
作用:用e返回L中第i個元素的值
*/
Status GetElem(SLinkList L, int n, int i, ElemType &e) {
//l是臨時變量,k存儲了第i個結點在數組中的位置(初始值任意)
int l, k = n;
//檢查參數i是否合法,i是否越界
if(i < 1 || i > ListLength(L, n)){
printf("輸入非法!\n");
return ERROR;
}//if
//順着鏈表查找第i個結點,循環退出時k保存了第i個結點在數組中的位置
for(l = 1; l <= i; l++){
//k記錄了下一個結點在數組中的位置
k = L[k].cur;
}//for
//e保存了第i個元素的值
e = L[k].data;
//操作成功
return OK;
}//GetElem
/*
函數:SetElem
參數:SLinkList L 靜態鏈表L
int n 靜態鏈表數據鏈表頭結點在數組中的位置
int i 修改第i個位置的元素的值
ElemType e 修改第i個位置的元素值爲e
返回值:狀態碼,OK表示操作成功,ERROR表示操作失敗
作用:修改L中第i個元素的值爲e
*/
Status SetElem(SLinkList L, int n, int i, ElemType e) {
//l是臨時變量,k存儲了第i個結點在數組中的位置(初始值任意)
int l, k = n;
//檢查參數i是否合法,i是否越界
if(i < 1 || i > ListLength(L, n)){
printf("輸入非法!\n");
return ERROR;
}//if
//順着鏈表查找第i個結點,循環退出時k保存了第i個結點在數組中的位置
for(l = 1; l <= i; l++) {
//k記錄了下一個結點在數組中的位置
k = L[k].cur;
}//for
//將第i個元素的值設置爲k
L[k].data = e;
//操作成功
return OK;
}//SetElem
/*
函數:PriorElem
參數:SLinkList L 靜態鏈表L
int n 靜態鏈表數據鏈表頭結點在數組中的位置
ElemType cur_e 線性表中的數據元素,且不是第一個元素
ElemType &pre_e 帶回cur_e的前驅結點的值
返回值:狀態碼,OK表示操作成功,ERROR表示操作失敗
作用:若cur_e是線性表L中的數據元素,且不是第一個,則用pre_e返回它的前驅;
否則操作失敗,pre_e無定義
*/
Status PriorElem(SLinkList L, int n, ElemType cur_e, ElemType &pre_e){
//i指示了靜態鏈表數據鏈表的頭結點在數組中的位置
//j的含義看do-while循環裏面的說明
int j, i = L[n].cur;
//從數據鏈表的首元結點開始向後逐個查找,由於靜態鏈表沒有設置成雙向的,
//沒有前驅指針域保存結點的前驅信息,所以不能直接用LocateElem函數來定位,
//我們需要另寫代碼來取出某個結點的前驅在數組中的位置,並且需要單獨設置
//變量保存結點的前驅,直到遇到值爲 cur_e 的結點(找到了)或到達鏈表末尾(沒找到)爲止。
do{
//循環結束時:
//1.如果找到了值爲cur_e的元素,那麼i存儲了值爲cur_e的結點在數組的位置
// 那麼j指示的是i所指示結點的前驅結點在數組中的位置。
//2.如果沒有找到,那麼i的值爲0,j的值爲數據鏈表最後一個結點在數組中的位置。
j = i;
//i指示了下一個結點在數組中的位置
i = L[i].cur;
}while(i && cur_e != L[i].data);
//while(i && cur_e != L[i].data) <=> while(i != 0 && cur_e != L[i].data)
//i不爲0表示鏈表沒有走到末尾,也就是循環中途結束,也就是找到了值爲cur_e的結點
if(i){ //if(i) <=> if(i != 0)
//取出前驅結點的值並保存到pre_e
pre_e = L[j].data;
//操作成功
return OK;
}//if
//操作失敗
return ERROR;
}//PriorElem
/*
函數:NextElem
參數:SLinkList L 靜態鏈表L
int n 靜態鏈表數據鏈表頭結點在數組中的位置
ElemType cur_e 線性表中的數據元素,且不是最後一個
ElemType &next_e 帶回cur_e的後繼結點的值
返回值:狀態碼,OK表示操作成功,ERROR表示操作失敗
作用:若cur_e是線性表L中的數據元素,且不是最後一個,則用next_e返回它的後繼;
否則操作失敗。
*/
Status NextElem(SLinkList L, int n, ElemType cur_e, ElemType &next_e){
//在L中查找第一個值爲cur_e的元素的位置
int i = LocateElem(L, n, cur_e);
//i的值不爲0表示靜態鏈表數據鏈表L中存在值爲cur_e的結點,
//此時i指示了該結點在數組中的位置。
if(i){ //if(i) <=> if(i != 0)
//使i保存cur_e的後繼結點在數組中的位置
i = L[i].cur;
//i的值不爲0說明cur_e有後繼
if(i){ //if(i) <=> if(i != 0)
//取出後繼元素的值並保存到next_e
next_e = L[i].data;
//操作成功,cur_e元素有後繼併成功獲得後繼的值
return OK;
}//if
}//if
//操作失敗,L中不存在cur_e元素,cur_e元素無後繼
return ERROR;
}//NextElem
/*
函數:ListInsert
參數:SLinkList L 靜態鏈表L
int n 靜態鏈表數據鏈表頭結點在數組中的位置
int i 在第i個位置之前插入
ElemType e 插入的值爲e
返回值:狀態碼,OK表示操作成功,ERROR表示操作失敗
作用:在靜態鏈表L中第i個元素之前插入新的數據元素e
*/
Status ListInsert(SLinkList L, int n, int i, ElemType e) {
//l是循環用的臨時變量
//j指示了新申請結點在數組中的位置
//k將會用於存儲第i-1個結點在數組中的位置(初始值:數據鏈表頭結點在數組中的位置)
int l, j, k = n;
//檢查i的值是否合法,i是否越界
if(i < 1 || i > ListLength(L, n) + 1) {
return ERROR;
}//if
//從備用鏈表摘下一個空閒結點,申請一個結點的空間
j = Malloc(L);
//檢查申請結點是否成功,若申請成功則執行插入操作
//申請成功
if(j){ //if(j) <=> if(j != 0)
//將e保存到新申請的結點中
L[j].data = e;
//找到第i-1個結點
for(l = 1; l < i; l++) {
//k的初始值爲數據鏈表頭結點在數組中的位置
//循環結束後k的值是第i-1個結點在數組中的位置
k = L[k].cur;
}//for
//將新的結點插入到原來的鏈條中第i-1個結點後面,作爲新的第i個結點
//將第i個及其後面的結點鏈接到新申請的結點後面
L[j].cur = L[k].cur;
//將新申請的結點及其後續結點鏈接到第i-1個結點的後面
L[k].cur = j;
//操作成功
return OK;
}//if
//操作失敗
return ERROR;
}//ListInsert
/*
函數:ListDelete
參數:SLinkList L 靜態鏈表L
int n 靜態鏈表數據鏈表頭結點在數組中的位置
int i 在第i個位置刪除
ElemType &e 帶回被刪除的值e
返回值:狀態碼,OK表示操作成功,ERROR表示操作失敗
作用:刪除表中第i個位置的數據元素e,並返回其值
*/
Status ListDelete(SLinkList L, int n, int i, ElemType &e) {
//j將會用於存儲第i個結點在數組中的位置
//k將會用於存儲第i-1個結點在數組中的位置(初始值:數據鏈表頭結點在數組中的位置)
int j, k = n;
//檢查i的值是否合法,i是否越界
if(i < 1 || i > ListLength(L, n)) {
return ERROR;
}//if
//找到第i-1個結點
for(j = 1; j < i; j++) {
//k的初始值爲數據鏈表頭結點在數組中的位置
//循環結束後k的值是第i-1個結點在數組中的位置
k = L[k].cur;
}//for
//先保存待刪除的第i個結點在數組中的位置,j指示了這一位置
j = L[k].cur;
//將第i+1個結點在數組中的位置填在第i-1個結點的cur中,
//將第i個結點從靜態鏈表的數據鏈表中隔離出來
L[k].cur = L[j].cur;
//取出被刪除結點中存儲的值並保存到e
e = L[j].data;
//釋放掉被刪結點的空間,使之重新回到備用鏈表中,可以作爲空閒結點再次被使用
Free(L,j);
//操作成功
return OK;
}//ListDelete
/*
函數:difference
參數:SLinkList space
int &S
返回值:無
作用:(求集合(A-B)∪(B-A))依次輸入集合A和B的元素,在一維數組L中建立表示
集合(A-B)∪(B-A) 的鏈表,S爲其頭指針。
假設備用空間足夠大,L[0].cur爲其頭指針
*/
void difference(SLinkList space, int &S) {
//m用於保存從鍵盤接收的集合A元素的個數
//n用於保存從鍵盤接收的集合B元素的個數
//i和j都是臨時變量
//r保存了集合A的尾元結點在數組中的位置,在靜態鏈表中是集合A和B元素的分界線
//p和k的作用看後續說明
int m, n, i, r, p, k;
//臨時變量,用於保存從鍵盤輸入的集合B的元素值
ElemType b;
//初始化備用鏈表
InitSpace(space);
//生成靜態鏈表S數據鏈表的頭結點
S = Malloc(space);
//r暫時指向S的當前最後結點
r = S;
//從鍵盤接收A和B的元素個數
printf("->請輸入集合A和B的元素個數(逗號隔開):");
scanf("%d,%d%*c", &m, &n);
//從鍵盤接收集合A的每個元素,並使用它們建立的靜態鏈表
printf("->請輸入集合A中的元素(共%d個)空格隔開\n", m);
for(int j = 1; j <=m; ++j){
//從備用鏈表摘下一個空閒結點,申請一個結點的空間
i = Malloc(space);
//從鍵盤輸入A的元素值並保存到新申請結點的數據域中
scanf("%c", &space[i].data); getchar();
//將這個新結點鏈接到鏈表最後一個結點後面,成爲新的尾元結點
space[r].cur = i;
//使r指向新的尾元結點,但是r僅僅暫時指向尾元結點,待集合B元素輸入後
//r將有可能不再指向靜態鏈表尾元結點,而是作爲靜態鏈表S中集合A和B元素的分界線
r = i;
}//for
//設置靜態鏈表A尾元結點的指針爲空
space[r].cur = 0;
//從鍵盤接收集合B的每個元素,若不在當前表中,則插入,否則刪除
//可以考慮用基本操作替換掉插入和刪除部分的代碼
printf("->請輸入集合B中的元素(共%d個)空格隔開\n",n);
for(int j = 1; j <= n; ++j){
//從鍵盤接收集合B的元素值
scanf("%c", &b); getchar();
//p保存了靜態鏈表S的頭結點在數組中的位置
p = S;
//k指向集合A的首元結點
k = space[S].cur;
//在當前表中查找
//由於r指向靜態鏈表S數據鏈表的尾元結點,所以space[r].cur的值爲0
//while(k != space[r].cur && space[k].data != b)
// <=> while(k != 0 && space[k].data != b)
while(k != space[r].cur && space[k].data != b){
//若在靜態鏈表S中找到找到值爲b的結點,說明集合A中已經存在值b
//循環退出時,k指示了值爲b的結點在數組中的位置,
//p指示了k指向的結點的前驅結點在數組中的位置
//若沒有找到值爲b的元素,鏈表會一直掃描到末尾,k的值爲0
//p指示了靜態鏈表S數據鏈表的尾元結點
p = k;
//指針後移,類似於單鏈表中的p=p->next;
k = space[k].cur;
} //while
//當前表中不存在該元素,插入在r所指結點之後,且r的位置不變
//if(k == space[r].cur) <=> if(k == 0)
if(k == space[r].cur){
//從備用鏈表摘下一個空閒結點,申請一個新的結點的空間
i = Malloc(space);
//將從鍵盤接收的集合B的元素值存入新結點的數據域
space[i].data = b;
//將新結點的指針域置爲空
//因爲r指示尾元結點,所以space[r].cur值爲0,
//所以space[i].cur = space[r].cur;相當於space[i].cur = 0;
space[i].cur = space[r].cur;
//將新結點插入到r後面,但是r的值不能變,因爲r前面的是集合A的元素
//r的後面是集合B的元素,所以r是靜態鏈表中集合A和集合B元素的分界線
space[r].cur = i;
//由代碼可以看出,集合B元素的插入並不是發生在靜態鏈表數據鏈表的末尾,
//而是發生在r位置之後,所以它的效果就相當於集合A的元素採用尾插法,
//集合B的元素採用頭插法,所以輸出時可以看到,集合A的元素是正序輸出
//集合B的元素是逆序輸出,注意觀察調試時輸出的結果。
} //if
else{ //當前元素已在表中,刪除之
//k指示了值爲b的結點在數組中的位置,所以space[k].cur指示該節點的後繼
//p指示了k指向的結點的前驅結點在數組中的位置
//這句代碼的作用是:將k的後繼接到k的前驅後面,將k從數據鏈表中隔離出來
space[p].cur = space[k].cur;
//釋放掉k所指的結點空間,使之迴歸備用鏈表,供後續操作使用
Free(space, k);
//r起着集合A和B元素分界線的作用,r指向集合A最後一個元素。
//刪除操作總是刪除集合A中的元素,所以刪除位置k的值有可能取到r。
//如果刪除了k位置的結點,k位置的結點將會迴歸到備用鏈表中,
//但是r卻沒有改變,那麼此時r將會指向一個被回收的結點。
//假如此次刪除操作後緊接着發生一次插入操作,那麼插入操作將會
//發生在備用鏈表中,這顯然是錯誤的,沒有達到我們想要的結果。
//若想解決這個問題,就必須在刪除操作完成後及時將r的值改變,
//使其指向被刪除結點的前驅結點。
//所以若刪除的是r所指結點,則需要修改尾指針,使r仍然指向集合A的最後一個元素。
if(r == k) { //k指示了被刪除結點在數組中的位置
//此時,p指向k指向結點的前驅,應該讓r指向這個結點
r = p;
}//if
}//else
}//for
}//difference
//**********************************************主函數********************************************
int main(int argc,char *argv[]){
printf("\n---------------------------------靜態鏈表引用版---------------------------------\n");
//備用鏈表頭結點地址
SLinkList L;
//La是靜態鏈表A的頭結點的在數組中的位置
//Lb是靜態鏈表B的頭結點的在數組中的位置
int La, Lb;
//m是從鍵盤接收的靜態鏈表A的元素個數
//n是從鍵盤接收的靜態鏈表B的元素個數
int m, n;
//-------------------------------初始化備用鏈表-----------------------------
printf("->初始化備用鏈表\n");
InitSpace(L);
printf("->備用鏈表初始化完成。\n\n");
//-------------------------------初始化靜態鏈表A----------------------------
La = InitList(L);
printf("->初始化靜態鏈表A的頭結點完成!\n");
printf("->靜態鏈表A是否爲空:");
if(ListEmpty(L, La) == TRUE) {
printf("是\n");
}//if
else {
printf("否\n");
}//else
printf("->靜態鏈表A的表長:%d\n", ListLength(L, La));
printf("->請輸入靜態鏈表A的元素個數(整數),輸入後按回車確認:");
scanf("%d", &m); getchar();
//從鍵盤接收集合A的每個元素,並使用它們建立的靜態鏈表
printf("->請輸入靜態鏈表A中的元素(共%d個)空格隔開\n", m);
for(int i = 1; i <= m; i++) {
//先申請一個新的空閒結點,得到其在數組中的位置
int j = Malloc(L);
//從鍵盤接收新的數據
ElemType e;
scanf("%c", &e); getchar();
//將新結點插入到鏈表中
ListInsert(L, La, i, e);
}//for
printf("->靜態鏈表A初始化之後所有結點元素值如下:\n");
ListTraverse(L, La, Print);
printf("->靜態鏈表A是否爲空:");
if(ListEmpty(L, La) == TRUE) {
printf("是\n");
}//if
else {
printf("否\n");
}//else
printf("->靜態鏈表A的表長:%d\n", ListLength(L, La));
//------------------------------初始化靜態鏈表B--------------------------
Lb = InitList(L);
printf("\n->初始化靜態鏈表B的頭結點完成!\n");
printf("->靜態鏈表B是否爲空:");
if(ListEmpty(L, Lb) == TRUE) {
printf("是\n");
}//if
else {
printf("否\n");
}//else
printf("->靜態鏈表B的表長:%d\n", ListLength(L, Lb));
printf("->請輸入靜態鏈表B的元素個數(整數),輸入後按回車確認:");
scanf("%d", &n); getchar();
//從鍵盤接收集合B的每個元素,並使用它們建立的靜態鏈表
printf("->請輸入靜態鏈表B中的元素(共%d個)空格隔開\n", n);
for(int i = 1; i <= n; i++) {
//先申請一個新的空閒結點,得到其在數組中的位置
int j = Malloc(L);
//從鍵盤接收新的數據
ElemType e;
scanf("%c", &e); getchar();
//將新結點插入到鏈表中
ListInsert(L, Lb, i, e);
}//for
printf("->靜態鏈表B初始化之後所有結點元素值如下:\n");
ListTraverse(L, Lb, Print);
printf("->靜態鏈表B是否爲空:");
if(ListEmpty(L, Lb) == TRUE) {
printf("是\n");
}//if
else {
printf("否\n");
}//else
printf("->靜態鏈表B的表長:%d\n", ListLength(L, Lb));
//--------------------------測試從鏈表中取值-------------------
printf("\n->您想取靜態鏈表A第幾個位置的值(1-%d):", m);
int position = 1;
scanf("%d", &position); getchar();
printf("->靜態鏈表A第%d個的值爲:", position);
int result = 0;
ElemType elem;
result = GetElem(L, La, position, elem);
if(result == ERROR) {
printf("沒找到!\n");
}//if
else {
printf("%c\n", elem);
}//else
printf("\n->您想取靜態鏈表B第幾個位置的值(1-%d):", n);
scanf("%d", &position); getchar();
printf("->靜態鏈表B第%d個的值爲:", position);
result = 0;
result = GetElem(L, Lb, position, elem);
if(result == ERROR) {
printf("沒找到!\n");
}//if
else {
printf("%c\n", elem);
}//else
//---------------------------測試按值查找-----------------------------------
printf("->您想在靜態鏈表A中查找的值爲:");
scanf("%c", &elem); getchar();
result = LocateElem(L, La, elem);
if(result == 0) {
printf("->沒有在靜態鏈表A中找到您輸入的值:%c\n", elem);
}//if
else {
printf("->找到了,值爲%c的結點在數組中的位序是:%d\n", elem, result);
}//else
printf("\n->您想在靜態鏈表B中查找的值爲:");
scanf("%c", &elem); getchar();
result = LocateElem(L, Lb, elem);
if(result == 0) {
printf("->沒有在靜態鏈表B中找到您輸入的值:%c\n", elem);
}//if
else {
printf("->找到了,值爲%c的結點在數組中的位序是:%d\n", elem, result);
}//else
//---------------------------測試設置值-----------------------------------
printf("\n->修改前靜態鏈表A所有結點元素值如下:\n");
ListTraverse(L, La, Print);
printf("->您想修改靜態鏈表A中哪個位置的值,請輸入位序(1-%d):", m);
scanf("%d", &position); getchar();
printf("\n->您想要將這個位置的值修改爲多少?");
scanf("%c", &elem); getchar();
result = SetElem(L, La, position, elem);
if(result == ERROR) {
printf("->修改失敗!\n");
}//if
else {
printf("->修改成功!\n");
}//else
printf("\n->修改後靜態鏈表A所有結點元素值如下:\n");
ListTraverse(L, La, Print);
printf("\n->修改前靜態鏈表B所有結點元素值如下:\n");
ListTraverse(L, Lb, Print);
printf("->您想修改靜態鏈表B中哪個位置的值,請輸入位序(1-%d):", n);
scanf("%d", &position); getchar();
printf("\n->您想要將這個位置的值修改爲多少?");
scanf("%c", &elem); getchar();
result = SetElem(L, Lb, position, elem);
if(result == ERROR) {
printf("->修改失敗!\n");
}//if
else {
printf("->修改成功!\n");
}//else
printf("->靜態鏈表B所有結點元素值如下:\n");
ListTraverse(L, Lb, Print);
printf("\n");
//--------------------測試找前驅和找後繼的操作--------------------------
printf("\n->靜態鏈表A所有結點元素值如下:\n");
ListTraverse(L, La, Print);
printf("->您想找靜態鏈表A中哪個結點的前驅結點和後繼結點,請輸入元素的值:");
scanf("%c", &elem); getchar();
ElemType pre_e = -1;
result = PriorElem(L, La, elem, pre_e);
if(result == ERROR && pre_e == -1) {
printf("->沒有找到該結點前驅!\n");
}//if
else {
printf("->該結點前驅的元素值爲:%c\n", pre_e);
}//else
ElemType next_e = -1;
result = NextElem(L, La, elem, next_e);
if(result == ERROR && next_e == -1) {
printf("->沒有找到該結點後繼!\n");
}//if
else {
printf("->該結點後繼的元素值爲:%c\n", next_e);
}//else
printf("->靜態鏈表B所有結點元素值如下:\n");
ListTraverse(L, Lb, Print);
printf("->您想找靜態鏈表B中哪個結點的前驅結點和後繼結點,請輸入元素的值:");
scanf("%c", &elem); getchar();
pre_e = -1;
result = PriorElem(L, Lb, elem, pre_e);
if(result == ERROR && pre_e == -1) {
printf("->沒有找到該結點前驅!\n");
}//if
else {
printf("->該結點前驅的元素值爲:%c\n", pre_e);
}//else
next_e = -1;
result = NextElem(L, Lb, elem, next_e);
if(result == ERROR && next_e == -1) {
printf("->沒有找到該結點後繼!\n");
}//if
else {
printf("->該結點後繼的元素值爲:%c\n", next_e);
}//else
//---------------------------測試插入操作------------------------
printf("\n->插入操作前靜態鏈表A所有結點元素值如下:\n");
ListTraverse(L, La, Print);
printf("->您想要在靜態鏈表A的哪個位置插入,請輸入位序(1-%d):", m);
scanf("%d", &position); getchar();
printf("\n->您想要在這個位置插入的值爲多少:");
scanf("%c", &elem); getchar();
result = ListInsert(L, La, position, elem);
if(result == ERROR) {
printf("->插入操作失敗!\n");
}//if
else {
printf("->插入操作成功!\n");
}//else
printf("->插入操作後靜態鏈表A所有結點元素值如下:\n");
ListTraverse(L, La, Print);
printf("\n->插入操作前靜態鏈表B所有結點元素值如下:\n");
ListTraverse(L, Lb, Print);
printf("->您想要在靜態鏈表B的哪個位置插入,請輸入位序(1-%d):", n);
scanf("%d", &position); getchar();
printf("\n->您想要在這個位置插入的值爲多少:");
scanf("%c", &elem); getchar();
result = ListInsert(L, Lb, position, elem);
if(result == ERROR) {
printf("->插入操作失敗!\n");
}//if
else {
printf("->插入操作成功!\n");
}//else
printf("->插入操作後靜態鏈表B所有結點元素值如下:\n");
ListTraverse(L, Lb, Print);
//---------------------------測試刪除操作----------------------------
printf("\n->刪除操作前靜態鏈表A所有結點元素值如下:\n");
ListTraverse(L, La, Print);
printf("->您想要在靜態鏈表A的哪個位置刪除,請輸入位序(1-%d):", m);
scanf("%d", &position); getchar();
result = ListDelete(L, La, position, elem);
if(result == ERROR) {
printf("->刪除操作失敗!\n");
}//if
else {
printf("->刪除操作成功!被刪除的元素值爲:%c\n", elem);
}//else
printf("->刪除操作後靜態鏈表A所有結點元素值如下:\n");
ListTraverse(L, La, Print);
printf("\n->刪除操作前靜態鏈表B所有結點元素值如下:\n");
ListTraverse(L, Lb, Print);
printf("->您想要在靜態鏈表B的哪個位置刪除,請輸入位序(1-%d):", n);
scanf("%d", &position); getchar();
result = ListDelete(L, Lb, position, elem);
if(result == ERROR) {
printf("->刪除操作失敗!\n");
}//if
else {
printf("->刪除操作成功!被刪除的元素值爲:%c\n", elem);
}//else
printf("->刪除操作後靜態鏈表B所有結點元素值如下:\n");
ListTraverse(L, Lb, Print);
//--------------------測試集合 (A-B)∪(B-A)的求解--------------------
printf("\n->測試求集合 (A-B)∪(B-A):\n");
SLinkList space;
int S;
difference(space, S);
printf("\n->不屬於集合A ∩B的元素如下:(注意集合B元素的輸出順序:逆向輸出)\n");
ListTraverse(space, S, Print);
return 0;
}
以下是程序測試時輸入的數據和輸出:
---------------------------------靜態鏈表引用版---------------------------------
->初始化備用鏈表
->備用鏈表初始化完成。
->初始化靜態鏈表A的頭結點完成!
->靜態鏈表A是否爲空:是
->靜態鏈表A的表長:0
->請輸入靜態鏈表A的元素個數(整數),輸入後按回車確認:5
->請輸入靜態鏈表A中的元素(共5個)空格隔開
1 2 3 4 5
->靜態鏈表A初始化之後所有結點元素值如下:
1 2 3 4 5
->靜態鏈表A是否爲空:否
->靜態鏈表A的表長:5
->初始化靜態鏈表B的頭結點完成!
->靜態鏈表B是否爲空:是
->靜態鏈表B的表長:0
->請輸入靜態鏈表B的元素個數(整數),輸入後按回車確認:5
->請輸入靜態鏈表B中的元素(共5個)空格隔開
6 7 8 9 0
->靜態鏈表B初始化之後所有結點元素值如下:
6 7 8 9 0
->靜態鏈表B是否爲空:否
->靜態鏈表B的表長:5
->您想取靜態鏈表A第幾個位置的值(1-5):2
->靜態鏈表A第2個的值爲:2
->您想取靜態鏈表B第幾個位置的值(1-5):3
->靜態鏈表B第3個的值爲:8
->您想在靜態鏈表A中查找的值爲:3
->找到了,值爲3的結點在數組中的位序是:7
->您想在靜態鏈表B中查找的值爲:4
->沒有在靜態鏈表B中找到您輸入的值:4
->修改前靜態鏈表A所有結點元素值如下:
1 2 3 4 5
->您想修改靜態鏈表A中哪個位置的值,請輸入位序(1-5):2
->您想要將這個位置的值修改爲多少?9
->修改成功!
->修改後靜態鏈表A所有結點元素值如下:
1 9 3 4 5
->修改前靜態鏈表B所有結點元素值如下:
6 7 8 9 0
->您想修改靜態鏈表B中哪個位置的值,請輸入位序(1-5):3
->您想要將這個位置的值修改爲多少?1
->修改成功!
->靜態鏈表B所有結點元素值如下:
6 7 1 9 0
->靜態鏈表A所有結點元素值如下:
1 9 3 4 5
->您想找靜態鏈表A中哪個結點的前驅結點和後繼結點,請輸入元素的值:2
->沒有找到該結點前驅!
->沒有找到該結點後繼!
->靜態鏈表B所有結點元素值如下:
6 7 1 9 0
->您想找靜態鏈表B中哪個結點的前驅結點和後繼結點,請輸入元素的值:0
->該結點前驅的元素值爲:9
->沒有找到該結點後繼!
->插入操作前靜態鏈表A所有結點元素值如下:
1 9 3 4 5
->您想要在靜態鏈表A的哪個位置插入,請輸入位序(1-5):1
->您想要在這個位置插入的值爲多少:4
->插入操作成功!
->插入操作後靜態鏈表A所有結點元素值如下:
4 1 9 3 4 5
->插入操作前靜態鏈表B所有結點元素值如下:
6 7 1 9 0
->您想要在靜態鏈表B的哪個位置插入,請輸入位序(1-5):2
->您想要在這個位置插入的值爲多少:8
->插入操作成功!
->插入操作後靜態鏈表B所有結點元素值如下:
6 8 7 1 9 0
->刪除操作前靜態鏈表A所有結點元素值如下:
4 1 9 3 4 5
->您想要在靜態鏈表A的哪個位置刪除,請輸入位序(1-5):1
->刪除操作成功!被刪除的元素值爲:4
->刪除操作後靜態鏈表A所有結點元素值如下:
1 9 3 4 5
->刪除操作前靜態鏈表B所有結點元素值如下:
6 8 7 1 9 0
->您想要在靜態鏈表B的哪個位置刪除,請輸入位序(1-5):2
->刪除操作成功!被刪除的元素值爲:8
->刪除操作後靜態鏈表B所有結點元素值如下:
6 7 1 9 0
->測試求集合 (A-B)∪(B-A):
->請輸入集合A和B的元素個數(逗號隔開):5,5
->請輸入集合A中的元素(共5個)空格隔開
1 2 3 4 5
->請輸入集合B中的元素(共5個)空格隔開
3 4 5 6 7
->不屬於集合A ∩B的元素如下:(注意集合B元素的輸出順序:逆向輸出)
1 2 7 6
--------------------------------
Process exited with return value 0
Press any key to continue . . .
總結:
細細閱讀並實現過的童鞋們應該能對書中的算法有所體會,靜態鏈表中包含了大量的程序處理技巧,我在註釋中都有說明,希望大家細細研讀書上的算法,有所收穫。
下次我們會介紹雙向循環鏈表的操作,希望大家繼續關注我的博客,再見!