學習數據結構,進行單鏈表操作是很基礎的內容;只要掌握單鏈表,那么循環鏈表、棧和隊列的操作將是水到渠成的事情。單鏈表的難點在於結構體和指針的配合使用,這點掌握熟練,那么單鏈表也不在話下。這篇文章的示例程序是在Ubuntu16.04操作系統環境中進行的。
我們學習鏈表的目的是什么?也就是說我們學習鏈表是要解決什么樣的問題呢?大家都知道,針對數組,一組數據的數據類型少,產生了結構體,而結構體和數組都有一個共同的特點,在定義的時候就要規定明確的數據個數。所以對於有限個數據的處理,我們使用結構體或數組就夠用了,但是,很多實際問題,我們無法在定義的時候能夠明確具體的數據個數,很多時候會有未知個數的數據需要處理,這時我們就需要使用鏈表來進行操作了。
下面我們便以代碼為例,簡單說明一下單鏈表的操作。
首先編寫頭文件,頭文件的名稱為:linklist.h。聲明結構體,聲明各個操作函數。一般的單鏈表操作,是不會在節點中加入編號的,而我個人認為,加入編號方便后續的編程實現,也不容易產生混亂,可以進一步驗證正確與否,盡管這樣做,對於代碼編寫難度略有提高。
1 /* 2 * 文件名為:linklist.h 3 * 文件作用:聲明結構體,聲明各個操作函數,便於主函數的調用 4 * 文件用途:用於單鏈表的操作使用 5 */ 6 typedef int datatype; /*自定義變量datatype 便於閱讀。*/ 7 /* 定義結構體部分*/ 8 typedef struct node{ 9 datatype data; 10 int no; 11 struct node * next; 12 }listnode,*linklist; 13 14 /* 聲明各個操作函數*/ 15 /* 聲明創建空表函數,返回值為空表指針*/ 16 linklist list_create(void); 17 /* 聲明頭結點插入節點函數,返回值為操作是否成功,成功:0失敗:-1*/ 18 int list_head_insert(linklist H,datatype x); 19 /* 聲明按節點編號插入節點函數,返回值為操作是否成功,成功:0失敗:-1*/ 20 int list_pos_insert(linklist H,datatype x,int pos); 21 /* 聲明按數值查找函數,返回值為節點編號,失敗:-1*/ 22 int list_number_search(linklist H,datatype x); 23 /* 聲明按節點序號查找函數,返回值為該節點編號下的數值,失敗:-1*/ 24 int list_pos_search(linklist H,int pos); 25 /* 聲明刪除指定節點編號的節點函數,返回值為操作是否成功,成功:0失敗:-1*/ 26 int list_delete(linklist H,int pos); 27 /* 聲明將鏈表數據全部倒置,返回值為操作是否成功,成功:0失敗:-1*/ 28 int list_invode(linklist H); 29 /* 聲明鏈表輸出顯示各個節點數據的函數,返回值為操作是否成功,成功:0失敗:-1*/ 30 int list_show(linklist H);
以上便是頭文件的編寫,下面創建linklist.c文件,用於各個函數功能的實現。由於文件中代碼行數較多,不方便講述,所以下面的講述,不是將代碼整段貼在下面,而是以函數為單位進行了分割,組合起來和源文件也是一模一樣的。
第一個函數功能是創建空表,函數沒有參數,但是返回值是指向創建空表的指針。首先要創建動態內存,賦值給空表指針,判斷指針是否為空來確定動態內存是否分配成功,若成功,則繼續給空表內的各個數值及指針賦值。最后返回指針,完成空表的創建。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "linklist.h" 4 linklist list_create(void) 5 { 6 linklist H = NULL; 7 /* 申請動態內存,賦值給指針*/ 8 H = (linklist)malloc(sizeof(listnode)); 9 /* 判斷動態內存是否申請成功*/ 10 if(H == NULL) 11 { 12 printf("no memory\n"); 13 return NULL; 14 }else 15 { 16 /* 給空表中結構體中的各個成員賦值*/ 17 H -> data = 0; 18 H -> no = -1; 19 H -> next = NULL; 20 /* 返回空表指針*/ 21 return H; 22 } 23 }
第二個函數功能是,在頭結點之后插入結點元素。函數有兩個參數,第一個參數是:所要插入節點的鏈表指針,第二個參數是:插入節點中結構體成員中數據的數值。返回值為操作是否成功,若成功則返回0,若失敗則返回-1。
首先要判斷傳參傳入的鏈表指針是否為空,若為空說明是一個失敗的鏈表,不能繼續執行插入操作,因此返回-1,就此程序結束;若不為空則繼續執行插入操作。在執行插入之前先要創建一個節點,節點的創建也需要申請動態內存管理空間,將動態內存申請的節點指針,賦予頭結點的節點指針,頭結點的節點指針賦予動態內存申請的指針,將參數傳入的數據賦值給新創建的節點結構體中的成員,成員編號賦予0,表示頭結點的下一個編號。由於每次調用這個函數,我們不能確定是第幾次給頭結點插入節點,因此節點的編號必然需要刷新,這樣構建循環,在循環中讓插入的節點的下一個節點依次自增1,使編號不會混亂。最后返回0值表明程序順利執行。
1 int list_head_insert(linklist H,datatype x) 2 { 3 linklist P = NULL; 4 /* 判斷傳入的鏈表是否為空*/ 5 if(H == NULL) 6 { 7 printf("linklist H is NULL\n"); 8 return -1; 9 }else 10 { 11 /* 創建節點,動態內存*/ 12 P = (linklist)malloc(sizeof(listnode)); 13 if(P == NULL) 14 { 15 printf("no memory\n"); 16 return -1; 17 }else 18 { 19 /* 插入節點操作,直接賦值*/ 20 P -> next = H -> next; 21 H -> next = P; 22 P -> data = x; 23 P -> no = H -> no + 1; 24 /* 刷新各個節點的編號*/ 25 while(P -> next != NULL) 26 { 27 P -> next -> no ++; 28 P = P -> next; 29 } 30 return 0; 31 } 32 } 33 }
第三個函數的功能是,依據節點編號插入節點。函數有三個參數,第一個參數是:所要插入節點的鏈表指針;第二個參數是:所要插入節點的數據值;第三個參數是:所要插入節點在鏈表中的節點序號。返回值為操作是否成功,若成功則返回0,若失敗則返回-1。
程序的開始依舊是判斷傳參傳入的鏈表指針是否為空,若不為空,則繼續執行,申請動態內存創建節點,首先要循環找到指定編號的指針的前一個指針(因為是單鏈表,所以沒有反向的連接以進行靈活的賦值),然后新創建的節點指針賦予指定編號的前一個節點指針,指定編號的前一個節點指針賦予新創建的指針,之后便是成員賦值和刷新編號。
1 int list_pos_insert(linklist H,datatype x,int pos) 2 { 3 linklist P = NULL; 4 linklist Q = NULL; 5 /* 判斷傳入的鏈表指針是否為空*/ 6 if(H == NULL) 7 { 8 printf("linklist H is NULL\n"); 9 return -1; 10 }else 11 { 12 /* 創建新節點*/ 13 P = (linklist)malloc(sizeof(listnode)); 14 if(P == NULL) 15 { 16 printf("no memory\n"); 17 return -1; 18 }else 19 { 20 /* 找到指定編號的前一個節點*/ 21 Q = H; 22 while(Q -> next -> no != pos) 23 { 24 Q = Q -> next; 25 } 26 /* 插入操作*/ 27 P -> next = Q -> next; 28 Q -> next = P; 29 /* 成員賦值*/ 30 P -> data = x; 31 /* 刷新節點編號*/ 32 P -> no = pos; 33 while(P -> next != NULL) 34 { 35 P -> next -> no ++; 36 P = P -> next; 37 } 38 Q = NULL; 39 P = NULL; 40 return 0; 41 } 42 } 43 }
第四個和第五個函數功能分別是按值查找和按位查找,代碼很簡單,也沒有復雜的刷新編號操作,在此就不再贅述。僅將代碼陳述其下,以供參考。
1 int list_number_search(linklist H,datatype x) 2 { 3 linklist P = NULL; 4 if(H == NULL) 5 { 6 printf("linklist H is NULL\n"); 7 return -1; 8 }else 9 { 10 P = H; 11 while(P -> data != x) 12 { 13 P = P -> next; 14 } 15 return P -> no; 16 } 17 } 18 19 /*按位查找的返回值是指定節點編號的數據值*/ 20 int list_pos_search(linklist H,int pos) 21 { 22 linklist P = NULL; 23 if(H == NULL) 24 { 25 printf("linklist H is NULL\n"); 26 return -1; 27 }else 28 { 29 P = H; 30 while(P -> no != pos) 31 { 32 P = P -> next; 33 } 34 return P -> data; 35 } 36 }
第六個函數功能是,刪除指定編號的節點。函數有兩個參數,第一個參數是:要刪除節點的鏈表的指針;第二個參數是:指定刪除的節點編號。返回值是操作是否成功,若成功則返回0,若失敗則返回-1。
第一步是要找到指定編號的節點前一個節點的指針。第二步指針賦值,第三部釋放刪除節點的動態內存空間,第四步刷新節點編號,第五步返回操作結果。
1 int list_delete(linklist H,int pos) 2 { 3 linklist P = NULL; 4 linklist Q = NULL; 5 /* 判斷傳入鏈表指針是否為空*/ 6 if(H == NULL) 7 { 8 printf("linklist H is NULL\n"); 9 return -1; 10 }else 11 { 12 /* 找到指定編號節點的前一個節點的指針*/ 13 P = H; 14 while(P -> next -> no != pos) 15 { 16 P = P -> next; 17 } 18 /* 刪除操作*/ 19 Q = P -> next; 20 P -> next = Q -> next; 21 free(Q); 22 Q = NULL; 23 /* 刷新節點編號*/ 24 while(P -> next != NULL) 25 { 26 P -> next -> no --; 27 P = P -> next; 28 } 29 P = NULL; 30 return 0; 31 } 32 }
第七個函數功能是,將鏈表中的節點依次倒置。函數只有一個參數,就是傳入待倒置的鏈表指針。返回值是操作是否成功,若成功則返回0,若失敗則返回-1。
實現倒置的原理很簡單,就是將鏈表一分為二,然后依次從頭結點重新插入,這樣便實現了倒置操作。着重提示的便是節點編號的刷新問題。
1 int list_invode(linklist H) 2 { 3 linklist P = NULL; 4 linklist Q = NULL; 5 /* 判斷傳入鏈表的指針是否為空*/ 6 if(H == NULL) 7 { 8 printf("linklist H is NULL\n"); 9 return -1; 10 }else 11 { 12 /* 將鏈表一分為二*/ 13 P = H -> next; 14 H -> next = NULL; 15 /* 依次從頭結點插入節點*/ 16 while(P -> next != NULL) 17 { 18 Q = P -> next; 19 P -> next = H -> next; 20 H -> next = P; 21 P -> no = 0; 22 /* 刷新結點編號*/ 23 while(P -> next != NULL) 24 { 25 P -> next -> no ++; 26 P = P -> next; 27 } 28 P = Q; 29 } 30 /* 為最后一個節點補充插入操作*/ 31 P -> next = H -> next; 32 H -> next = P; 33 P -> no = 0; 34 while(P -> next != NULL) 35 { 36 P -> next -> no ++; 37 P = P -> next; 38 } 39 return 0; 40 } 41 }
第八個函數功能是,輸出顯示鏈表數據。函數只有一個參數,待顯示數據的鏈表指針。返回值是操作結果是否成功,若成功則返回0,若失敗則返回-1。
1 int list_show(linklist H) 2 { 3 linklist P = NULL; 4 /* 判斷傳入鏈表指針是否為空*/ 5 if(H == NULL) 6 { 7 printf("linklist H is NULL\n"); 8 return -1; 9 }else 10 { 11 /* 輸出打印數值*/ 12 P = H -> next; 13 while(P != NULL) 14 { 15 printf("H -> data number %d is %d\n",P -> no,P -> data); 16 P = P -> next; 17 } 18 return 0; 19 } 20 }
以上便是linklist.c的文件內容,下面創建test.c的文件,用於測試函數的功能是否正確、完整。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "linklist.h" 4 int main() 5 { 6 linklist H = NULL; 7 /* 創建空鏈表*/ 8 H = list_create(); 9 /* 從頭結點插入節點*/ 10 list_head_insert(H,10); 11 list_head_insert(H,20); 12 list_head_insert(H,30); 13 list_head_insert(H,40); 14 list_head_insert(H,50); 15 list_show(H); 16 printf("insert 2=========================\n"); 17 /* 插入操作演示*/ 18 list_pos_insert(H,99,2); 19 list_show(H); 20 printf("delete 2=========================\n"); 21 /* 刪除操作演示*/ 22 list_delete(H,2); 23 list_show(H); 24 printf("invode===========================\n"); 25 /* 倒置操作演示*/ 26 list_invode(H); 27 list_show(H); 28 return 0; 29 }
之后便創建Makefile文件,進行編譯。
1 CFLAGS=-c –Wall –g 2 test:linklist.o test.o 3 .PHONY:clean 4 clean: 5 rm *.o test