單向鏈表每個節點由兩個成員組成:一個是數據域,一個是指向自身結構的指針類型成員。如: struct slist { int data; struct slist *next; }; typedef struct slist SLIST; 單向鏈表的基本演算法包括:鏈表的建立、節點數據域的輸出、節點的插 ...
單向鏈表每個節點由兩個成員組成:一個是數據域,一個是指向自身結構的指針類型成員。如:
struct slist
{
int data;
struct slist *next;
};
typedef struct slist SLIST;
單向鏈表的基本演算法包括:鏈表的建立、節點數據域的輸出、節點的插入和刪除。
(1) 建立帶有頭結點的單向鏈表
建立單向鏈表的主要步驟如下:
- 讀取數據
- 生成新節點
- 將數據存入節點的成員變數中
- 將新節點插入到鏈表中,重覆上述操作直至輸入結束。
例1 編寫函數 creat_slist,建立帶有頭結點的單向鏈表。節點數據域中的數值從鍵盤輸入,以 -1 作為輸入結束標誌。鏈表頭結點的地址由函數值返回。
program
1 //建立帶有頭結點的單向鏈表 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 //聲明結構體類型 SLIST 6 struct slist 7 { 8 int data; 9 struct slist *next; 10 }; 11 typedef struct slist SLIST; 12 13 //聲明鏈表建立函數 14 SLIST* creat_slist(); 15 void print_slist(SLIST* head); 16 void insert_snode(SLIST* head, int x, int y); 17 18 int main() 19 { 20 SLIST *head; 21 head = creat_slist(); //調用鏈表建立函數,得到頭結點地址 22 print_slist(head); 23 return 0; 24 } 25 26 //定義鏈表建立函數 27 SLIST* creat_slist() 28 { 29 //s指向新生成的節點;r指向鏈表的尾節點 30 SLIST *h, *s, *r; 31 int c; 32 33 h = (SLIST*)malloc(sizeof(SLIST)); 34 r = h; 35 scanf("%d", &c); //讀入數據 36 37 while (c != -1) //-1作為輸入結束標誌 38 { 39 s = (SLIST*)malloc(sizeof(SLIST)); //生成新節點 40 s->data = c; //將讀入的數據存入新節點的數據域 41 r->next = s; //新節點鏈接到表尾 42 r = s; //r指向當前表尾 43 scanf("%d", &c); //讀入數據 44 } 45 r->next = '\0'; //鏈表結束標誌 46 return h; 47 } 48 49 //順序輸出鏈表各節點數據 50 void print_slist(SLIST* head) 51 { 52 SLIST* p; 53 p = head->next; //p指向頭結點後的第一個節點 54 55 if (p == '\0') 56 { 57 printf("Linklist is null !\n"); //鏈表為空(只有頭結點) 58 } 59 else 60 { 61 printf("head"); 62 do 63 { 64 printf("->%d", p->data); //輸出當前節點數據域中的值 65 p = p->next; //p指向下一節點 66 } while (p != '\0'); //未到鏈表尾,迴圈繼續 67 } 68 } 69 70 //在值為x的節點前插入值為y的節點 71 void insert_snode(SLIST* head, int x, int y) 72 { 73 SLIST *s, *p, *q; 74 s = (SLIST*)malloc(sizeof(SLIST)); //生成新節點 75 s->data = y; //在新節點中存入y值 76 q = head; 77 p = head->next; //工作指針初始化,p指向第一個節點 78 while ( (p != '\0') && (p->data != x) ) 79 { 80 q = p; 81 p = p->next; //q指向p的前趨節點 82 } 83 //x存在,插在x之前;x不存在,p的值為NULL,插在表尾 84 s->next = p; 85 q->next = s; 86 }View Code
我們在函數中定義了一個名為 h 的指針變數,用於存放頭結點的地址;另外還定義了兩個工作指針:s 和 r,其中指針 s 用來指向新生成的節點,指針 r 總是指向當前鏈表的尾節點。每當把 s 所指的新開闢的節點連接到表尾後,r 便移向這一新的表尾節點,這時再用 s 去指向下一個新開闢的節點,就是使 r 承上,用 s 啟下。
鏈表最後一個節點的指針域中置 '\0'(NULL),作為單向鏈表的結束標誌。鏈表建成後,頭結點的地址作為 creat_slist 函數的返回值由 return 語句返回並賦給 main 函數中的指針變數 head,因此函數的類型應該是基類型為 SLIST 的指針類型。
調用 creat_slist1 函數時,若一開始就輸入-1,控制流程不會進入 while 迴圈,而直接執行迴圈之後的 r->next = '\0'; 語句。這時建立的是一個空鏈表,其結構如圖1所示。由此可見,判斷此鏈表是否為空鏈表,可用條件:h->next == '\0';
圖1 帶有頭結點的空鏈表
(2) 順序訪問鏈表中各節點的數據域
所謂“訪問”,可以理解為取各節點的數據域中的值進行各種運算、修改各節點的數據域中的值等一系列的操作。
輸出單向鏈表各節點數據域中的內容的演算法比較簡單,只需利用一個工作指針 p ,從頭到尾依次指向鏈表中的每個節點;當指針指向某個節點時,就輸出該節點數據域中的內容,直到遇到鏈表結束標誌為止。如果是空鏈表,就只輸出相關信息並返回調用函數。
例2 編寫函數 print_slist,順序輸出單向鏈表各節點數據域中的內容。
program
1 //建立帶有頭結點的單向鏈表 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 //聲明結構體類型 SLIST 6 struct slist 7 { 8 int data; 9 struct slist *next; 10 }; 11 typedef struct slist SLIST; 12 13 //聲明鏈表建立函數 14 SLIST* creat_slist(); 15 void print_slist(SLIST* head); 16 void insert_snode(SLIST* head, int x, int y); 17 18 int main() 19 { 20 SLIST *head; 21 head = creat_slist(); //調用鏈表建立函數,得到頭結點地址 22 print_slist(head); 23 return 0; 24 } 25 26 //定義鏈表建立函數 27 SLIST* creat_slist() 28 { 29 //s指向新生成的節點;r指向鏈表的尾節點 30 SLIST *h, *s, *r; 31 int c; 32 33 h = (SLIST*)malloc(sizeof(SLIST)); 34 r = h; 35 scanf("%d", &c); //讀入數據 36 37 while (c != -1) //-1作為輸入結束標誌 38 { 39 s = (SLIST*)malloc(sizeof(SLIST)); //生成新節點 40 s->data = c; //將讀入的數據存入新節點的數據域 41 r->next = s; //新節點鏈接到表尾 42 r = s; //r指向當前表尾 43 scanf("%d", &c); //讀入數據 44 } 45 r->next = '\0'; //鏈表結束標誌 46 return h; 47 } 48 49 //順序輸出鏈表各節點數據 50 void print_slist(SLIST* head) 51 { 52 SLIST* p; 53 p = head->next; //p指向頭結點後的第一個節點 54 55 if (p == '\0') 56 { 57 printf("Linklist is null !\n"); //鏈表為空(只有頭結點) 58 } 59 else 60 { 61 printf("head"); 62 do 63 { 64 printf("->%d", p->data); //輸出當前節點數據域中的值 65 p = p->next; //p指向下一節點 66 } while (p != '\0'); //未到鏈表尾,迴圈繼續 67 } 68 } 69 70 //在值為x的節點前插入值為y的節點 71 void insert_snode(SLIST* head, int x, int y) 72 { 73 SLIST *s, *p, *q; 74 s = (SLIST*)malloc(sizeof(SLIST)); //生成新節點 75 s->data = y; //在新節點中存入y值 76 q = head; 77 p = head->next; //工作指針初始化,p指向第一個節點 78 while ( (p != '\0') && (p->data != x) ) 79 { 80 q = p; 81 p = p->next; //q指向p的前趨節點 82 } 83 //x存在,插在x之前;x不存在,p的值為NULL,插在表尾 84 s->next = p; 85 q->next = s; 86 }View Code
(3)在單向鏈表中插入節點
在單向鏈表中插入節點,首先要確定插入的位置。當待插節點插在指針 p 所指的節點之前稱為“前插”;當待插節點插在指針 p 所指節點之後稱為“後插”。圖2示意了“前插”操作過程中各指針的指向。
圖2 單向鏈表中節點的插入
當進行“前插”操作時,需要三個工作指針:圖中s 用來指向新開闢的節點;用 p 指向插入的位置;q 指向 p 的前趨節點(由於是單向鏈表,沒有指針 q,就無法通過 p 去指向它前趨節點)。
例3 編寫函數 insert_snode,它的功能是:在值為 x 的節點前插入值為 y 的節點,若值為 x 的節點不存在,則插在表尾。
program
1 //建立帶有頭結點的單向鏈表 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 //聲明結構體類型 SLIST 6 struct slist 7 { 8 int data; 9 struct slist *next; 10 }; 11 typedef struct slist SLIST; 12 13 //聲明鏈表建立函數 14 SLIST* creat_slist(); 15 void print_slist(SLIST* head); 16 void insert_snode(SLIST* head, int x, int y); 17 18 int main() 19 { 20 SLIST *head; 21 head = creat_slist(); //調用鏈表建立函數,得到頭結點地址 22 print_slist(head); 23 return 0; 24 } 25 26 //定義鏈表建立函數 27 SLIST* creat_slist() 28 { 29 //s指向新生成的節點;r指向鏈表的尾節點 30 SLIST *h, *s, *r; 31 int c; 32 33 h = (SLIST*)malloc(sizeof(SLIST)); 34 r = h; 35 scanf("%d", &c); //讀入數據 36 37 while (c != -1) //-1作為輸入結束標誌 38 { 39 s = (SLIST*)malloc(sizeof(SLIST)); //生成新節點 40 s->data = c; //將讀入的數據存入新節點的數據域 41 r->next = s; //新節點鏈接到表尾 42 r = s; //r指向當前表尾 43 scanf("%d", &c); //讀入數據 44 } 45 r->next = '\0'; //鏈表結束標誌 46 return h; 47 } 48 49 //順序輸出鏈表各節點數據 50 void print_slist(SLIST* head) 51 { 52 SLIST* p; 53 p = head->next; //p指向頭結點後的第一個節點 54 55 if (p == '\0') 56 { 57 printf("Linklist is null !\n"); //鏈表為空(只有頭結點) 58 } 59 else 60 { 61 printf("head"); 62 do 63 { 64 printf("->%d", p->data); //輸出當前節點數據域中的值 65 p = p->next; //p指向下一節點 66 } while (p != '\0'); //未到鏈表尾,迴圈繼續 67 } 68 } 69 70 //在值為x的節點前插入值為y的節點 71 void insert_snode(SLIST* head, int x, int y) 72 { 73 SLIST *s, *p, *q; 74 s = (SLIST*)malloc(sizeof(SLIST)); //生成新節點 75 s->data = y; //在新節點中存入y值 76 q = head; 77 p = head->next; //工作指針初始化,p指向第一個節點 78 while ( (p != '\0') && (p->data != x) ) 79 { 80 q = p; 81 p = p->next; //q指向p的前趨節點 82 } 83 //x存在,插在x之前;x不存在,p的值為NULL,插在表尾 84 s->next = p; 85 q->next = s; 86 }View Code
由於本例中的單向鏈表採用了帶有頭結點的結構,不需要單獨處理新節點插在表頭的情況,從而簡化了操作。函數 insert_snode 中綜合運用了“查找”和“前插”的演算法。在進行插入操作的過程中,可能遇到三種情況,函數 insert_snode 將對這三種情況進行處理:
(1)鏈表非空,值為 x 的節點存在,新節點應插在該節點之前。
(2)鏈表非空,但值為 x 的節點不存在,新節點應插在表尾。
(3)鏈表為空表,這種情況相當於 x 的節點不存在,新節點應插在表尾,即插在頭結點之後,作為表的第一個節點。
在函數中,對於空表,在執行 p = head->next; 後,p 的值就為 NULL ,因此不會進入迴圈;當鏈表非空時進入迴圈,在 while 迴圈中,當出現 p == '\0' 時,將退出迴圈,這意味著查找結束,在鏈表中不存在值為 x 的節點。對於這兩種情況,語句 s->next = p; q->next = s; 將新節點插在表尾。當鏈表中存在值為 x 的節點時,p 的值一定部位 '\0',語句 s->next = p; q->next = s; 將新節點插在 x 節點之前。
需要註意的是:while 迴圈中兩個條件的順序不能對調。當 p == NULL 時,C 編譯程式將“短路”掉第二個條件,不做判斷;否則如果先判斷 p->data != x 的條件,由於 p 中的值已為 NULL,這時再要訪問 p->data, 就會發生訪問虛地址的錯誤操作。
(4)刪除單向鏈表中的節點
為了刪除單項鏈表中的某個節點,首先要找到待刪除節點的前趨節點,然後將此前趨節點的指針域去指向待刪除節點的後續節點(q->next = p->next),最後釋放被刪除節點所占空間 free(p) 即可。圖3示意了節點的刪除操作。
圖3 單向鏈表節點的刪除