近日周立功教授公開了數年的心血之作《程序設計與數據結構》,電子版已無償性分享到電子工程師與高校群體下載,經周立功教授授權,特對本書內容進行連載。
>>>>1.1.1 接口
在實際使用中,僅有添加到鏈表尾部、遍歷鏈表這些接口函數是不夠的。如在結點添加函數中,當前只是按照人們的習慣,將結點添加到鏈表尾部,使后添加的結點處在先添加的結點后面。而在編寫函數時知道,將一個結點添加至尾部的實現過程,需要修改原鏈表尾結點中p_next值,將其從NULL修改為指向新結點的指針。
雖然操作簡單,但執行該操作的前提是要找到添加結點前鏈表的尾結點,則需要從指向頭結點的p_head指針開始,依次遍歷每個結點,直到找到結點中p_next值為NULL(尾結點)時為止。可想而知,添加一個結點的效率將隨著鏈表長度的增加逐漸降低,如果鏈表很長,則效率將變得十分低下,因為每次添加結點前都要遍歷一次鏈表。
既然將結點添加到鏈表尾部會由于需要尋找尾結點而導致效率低下,何不換個思路,將結點添加到鏈表頭部。由于鏈表存在一個p_head指針指向頭結點,頭結點可以拿來就用,根本不要尋找,則效率將大大提高。將一個結點添加至鏈表頭部時,鏈表的變化詳見圖 3.11。
圖 3.11添加一個結點至鏈表頭部
在其實現過程中,需要完成兩個指針的修改:(1)修改新結點中的p_next,使其指向頭結點中p_next指向的結點;(2)修改頭結點的p_next,使其指向新的結點。
與添加結點至鏈表尾部的過程進行對比發現,其不再需要尋找尾結點的過程,無論鏈表多長,都可以通過這兩步完成結點的添加。加結點到鏈表頭部的函數原型(slist.h)為:
int slist_add_head (slist_head_t *p_head, slist_node_t *p_node);
其中,p_head指向鏈表頭結點,p_node為待添加的結點,其實現詳見程序清單3.21。
程序清單3.21 新增結點至鏈表頭部的范例程序
1 int slist_add_head (slist_head_t *p_head, slist_node_t *p_node)
2 {
3 p_node->p_next = p_head->p_next;
4 p_head->p_next = p_node;
5 return 0;
6 }由此可見,插入結點至鏈表頭部的程序非常簡單,無需查找且效率高,因此在實際使用時,若對位置沒有要求,則優先選擇將結點添加至鏈表頭部。
修改程序清單3.20中的一行代碼作為測試,比如,將第26行改為:
26 slist_add_head(&head, &(node3.node));
既然可以將結點添加至頭部和尾部,何不更加靈活一點,提供一個將結點至任意位置的接口函數呢?當結點添加至p_pos指向的結點之后,則鏈表的變化詳見圖 3.12。
圖 3.12 添加結點至任意位置示意圖
在其實現過程中,需要修改兩個指針:(1)修改新結點中的p_next,使其指向p_pos指向結點的下一個結點;(2)修改p_pos指向結點的p_next,使其指向新結點。通過這兩步即可添加結點,添加結點至鏈表任意位置的函數原型(slist.h)為:
int slist_add (slist_head_t *p_head, slist_node_t *p_pos, slist_node_t *p_node);
其中,p_head指向鏈表頭結點,p_node指向待添加的結點,p_pos指向的結點表明新結點添加的位置,新結點即添加在p_pos指向的結點后面,其實現詳見程序清單3.22。
程序清單3.22 新增結點至鏈表任意位置的范例程序
1 int slist_add (slist_head_t *p_head, slist_node_t *p_pos, slist_node_t *p_node)
2 {
3 p_node->p_next = p_pos->p_next;
4 p_pos->p_next = p_node;
5 return 0;
6 }盡管此函數在實現時沒有用到參數p_head,但還是將p_head參數傳進來了,因為實現其它功能時將會用到p_head參數,比如,判斷p_pos是否在鏈表中。
通過前面的介紹已經知道,直接將結點添加至鏈表尾部的效率很低,有了該新增結點至任意位置的函數后,如果每次都將結點添加到上一次添加的結點后面,同樣可以實現將結點添加至鏈表尾部。詳見程序清單3.23。
程序清單3.23 管理int型數據的范例程序
1 #include
2 #include "slist.h"
34 typedef struct _slist_int{
5 slist_node_t node; // 包含鏈表結點
6 int data; // int類型數據
7 } slist_int_t;
8
9 int list_node_process (void *p_arg, slist_node_t *p_node)
10 {
11 printf("%d ", ((slist_int_t *)p_node)->data);
12 return 0;
13 }
1415 int main (void)
16 {
17 slist_node_t head;
18 slist_int_t node1, node2, node3;
19 slist_init(&head);
20 node1.data = 1;
21 slist_add(&head, &head, &node1.node); // 添加 node1 至頭結點之后
22 node2.data = 2;23 slist_add(&head, &node1.node, &node2.node); // 添加 node2至node1之后
24 node3.data = 3;
25 slist_add(&head, &node2.node, &node3.node); // 添加 node3至node2之后
26 slist_foreach(&head, list_node_process, NULL); // 遍歷鏈表,用戶參數為NULL
27 return 0;
28 }顯然,添加結點至鏈表頭部和尾部,僅僅是添加結點至任意位置的特殊情況:
● 添加結點至鏈表頭部,即添加結點至頭結點之后;
● 添加結點至鏈表尾部,即添加結點至鏈表尾結點之后。
slist_add_head()函數和slist_add_tail()函數的實現詳見程序清單3.24。
程序清單3.24 基于slist_add()實現添加結點至頭部和尾部
1 int slist_add_tail (slist_head_t *p_head, slist_node_t *p_node)
2 {
3 slist_node_t *p_tmp = p_head; // 指向頭結點
4 while (p_tmp->p_next != NULL) { // 找到鏈表尾結點(直到結點的p_next的值為NULL)
5 p_tmp = p_tmp->p_next;6 }
7 return slist_add(p_head, p_tmp, p_node); // 添加結點至尾結點之后
8 }
9
10 int slist_add_head (slist_head_t *p_head, slist_node_t *p_node)
11 {
12 return slist_add(p_head, p_head, p_node); // 添加結點至頭結點之后
13 }如果要將一個結點添加至某一結點之前呢?實際上,添加結點至某一結點之前同樣也只是添加結點至某一結點之后的一種變形,即添加至該結點前一個結點的后面,詳見圖3.13。
圖3.13 添加結點至任意位置前示意圖
顯然,只要獲得某一結點的前驅,即可使用slist_add()函數添加結點至某一結點前面。為此,需要提供一個獲得某一結點前驅的函數,其函數原型(slist.h)為:
slist_node_t *slist_prev_get (slist_head_t *p_head, slist_node_t *p_pos);
其中,p_head指向鏈表頭結點,p_pos指向的結點表明查找結點的位置,返回值即為p_pos指向結點的前一個結點。由于在單向鏈表的結點中沒有指向其上一個結點的指針,因此,只有從頭結點開始遍歷鏈表,當某一結點的p_next指向當前結點時,表明其為當前結點的上一個結點,函數實現詳見程序清單3.25。
程序清單3.25 獲取某一結點前驅的范例程序
1 slist_node_t *slist_prev_get (slist_head_t *p_head, slist_node_t *p_pos)
2 {
3 slist_node_t *p_tmp = p_head; // 指向頭結點
4 while ((p_tmp != NULL) && (p_tmp->p_next != p_pos)) { // 找到p_pos指向的結點
5 p_tmp = p_tmp->p_next;
6 }
7 return p_tmp;
8 }由此可見,若p_pos的值為NULL,則當某一結點的p_next為NULL時就會返回,此時返回的結點實際上就是尾結點。為了便于用戶理解,可以簡單封裝一個查找尾結點的函數,其函數原型為:
slist_node_t *slist_tail_get (slist_head_t *p_head) ;
其函數實現詳見程序清單3.26。
程序清單3.26 查找尾結點
1 slist_node_t *slist_tail_get (slist_head_t *p_head)
2 {
3 return slist_prev_get(p_head, NULL);
4 }由于可以直接通過該函數得到尾結點,因此當需要將結添加點至鏈表尾部時,也就無需再自行查找尾結點了,修改slist_add_tail()函數的實現詳見程序清單3.27。
程序清單3.27 查找尾結點
1 int slist_add_tail (slist_head_t *p_head, slist_node_t *p_node)
2 {
3 slist_node_t *p_tmp = slist_tail_get(p_head); // 找到尾結點
4 return slist_add(p_head, p_tmp, p_node); // 添加結點至尾結點之后
5 }與添加一個結點對應,也可以從鏈表中刪除某一結點。假定鏈表中已經存在3個結點,現在要刪除中間的結點,則刪除前后的鏈表變化詳見圖3.14。
圖3.14 刪除結點示意圖
顯然,刪除一個結點也需要修改兩個指針的值:既要修改其上一個結點的p_next,使其指向待刪除結點的下一個結點,還要將刪除結點的p_next設置為NULL。
刪除結點的函數原型(slist.h)為:
int slist_del (slist_head_t *p_head, slist_node_t *p_node);
其中,p_head指向鏈表頭結點,p_node為待刪除的結點,slist_del()函數的實現詳見程序清單3.28。
程序清單3.28刪除結點范例程序
1 int slist_del (slist_head_t *p_head, slist_node_t *p_node)
2 {
3 slist_node_t *p_prev = slist_prev_get(p_head, p_node);//找到待刪除結點的上一個結點
4 if (p_prev) {
5 p_prev->p_next = p_node->p_next;
6 p_node->p_next = NULL;
7 return 0;
8 }
9 return -1;
10 }為便于查閱,如程序清單3.29所示展示了slist.h文件的內容。
程序清單3.29 slist.h文件內容
1 #pragma once
2
3 typedef struct _slist_node {
4 struct _slist_node *p_next; //指向下一個結點的指針
5 } slist_node_t;
6
7 typedef slist_node_t slist_head_t; //頭結點類型定義
8 typedef int (*slist_node_process_t) (void *p_arg, slist_node_t *p_node); //遍歷鏈表的回調函數類型
910 int slist_init (slist_head_t *p_head); //鏈表初始化
12 int slist_add (slist_head_t *p_head, slist_node_t *p_pos, slist_node_t *p_node); //添加一個結點
13 int slist_add_tail (slist_head_t *p_head, slist_node_t *p_node); //添加一個結點至鏈表尾部
14 int slist_add_head (slist_head_t *p_head, slist_node_t *p_node); //添加一個結點至鏈表頭部
15 int slist_del (slist_head_t *p_head, slist_node_t *p_node); //刪除一個結點
16
17 slist_node_t *slist_prev_get (slist_head_t *p_head, slist_node_t *p_pos); //尋找某一結點的前一結點
18 slist_node_t *slist_next_get (slist_head_t *p_head, slist_node_t *p_pos); //尋找某一結點的后一結點
19 slist_node_t *slist_tail_get (slist_head_t *p_head); //獲取尾結點
20 slist_node_t *slist_begin_get (slist_head_t *p_head); //獲取開始位置,第一個用戶結點
21 slist_node_t *slist_end_get (slist_head_t *p_head); //獲取結束位置,尾結點下一個結點的位置
22
23 int slist_foreach(slist_head_t *p_head, //遍歷鏈表
24 slist_node_process_t pfn_node_process,
25 void *p_arg);綜合范例程序詳見程序清單3.30。
程序清單3.30綜合范例程序
1 #include
2 #include "slist.h"
3
4 typedef struct _slist_int {
5 slist_node_t node; //包含鏈表結點
6 int data; // int類型數據
7 }slist_int_t;
8
9 int list_node_process (void *p_arg, slist_node_t *p_node)
10 {
11 printf("%d ", ((slist_int_t *)p_node)->data);
12 return 0;
13 }
14
15 int main(void)
16 {
17 slist_head_t head; //定義鏈表頭結點
18 slist_int_t nodel, node2, node3;
19 slist_init(&head);
20
21 node1.data = 1;
22 slist_add_tail(&head, &(node1.node));
23 node2.data = 2;
24 slist_add_tail(&head, &(node2.node));
25 node3.data = 3;
26 slist_add_head(&head, &(node3.node));
27 slist_del(&head, &(node2.node)); //刪除node2結點
28 slist_foreach(&head, list_node_process, NULL); //遍歷鏈表,用戶參數為NULL
29 return 0;
30 }程序中所有的結點都是按照靜態內存分配的方式定義的,即程序在運行前,各個結點占用的內存就已經被分配好了,而不同的是動態內存分配需要在運行時使用malloc()等函數完成內存的分配。
由于靜態內存不會出現內存泄漏,且在編譯完成后,各個結點的內存就已經分配好了,不需要再花時間去分配內存,也不需要添加額外的對內存分配失敗的處理代碼。因此,在嵌入式系統中,往往多使用靜態內存分配的方式。但其致命的缺點是不能釋放內存,有時候用戶希望在刪除鏈表的結點時,釋放掉其占用內存,這就需要使用動態內存分配。
實際上,鏈表的核心代碼只是負責完成鏈表的操作,僅需傳遞結點的地址(p_node)即可,鏈表程序并不關心結點的內存從何而來。基于此,若要實現動態內存分配,只要在應用中使用malloc()等動態內存分配函數即可,詳見程序清單3.31。
程序清單3.31綜合范例程序(使用動態內存)
1 #include
2 #include "slist.h"
3 #include
4
5 typedef struct _slist_int{
6 slist_node_t node; // 包含鏈表結點
7 int data; // int類型數據
8 } slist_int_t;
9
10 int list_node_process(void *p_arg, slist_node_t *p_node)
11 {
12 printf("%d ", ((slist_int_t *)p_node)->data);
13 return 0;
14 }
15
16 int my_list_add(slist_head_t *p_head, int data) // 插入一個數據
17 {
18 slist_int_t *p_node = (slist_int_t *)malloc(sizeof(slist_int_t));
19 if (p_node == NULL) {
20 printf("The malloc memory failed!");
21 return -1;
22 }
23 p_node->data = data;
24 slist_add_head(p_head, &(p_node->node)); // 將結點加入鏈表中
25 return 0;
26 }
27
28 int my_list_del (slist_head_t *p_head, int data) //刪除一條數據
29 {
30 slist_node_t *p_node = slist_begin_get(p_head);
31 slist_node_t *p_end = slist_end_get(p_head);
32 while (p_node != p_end){
33 if (((slist_int_t *)p_node)->data == data){
34 printf(" delete the data %d :", data);
35 slist_del(p_head, p_node); //刪除結點
36 free(p_node);
37 break;
38 }
39 p_node = slist_next_get(p_head, p_node);
40 }
41 slist_foreach(p_head, list_node_process, NULL); //刪除結點后,再打印出所有結點的數據信息
42 return 0;
43 }
44
45 int main(void)
46 {
47 slist_head_t *p_head = (slist_head_t *)malloc(sizeof(slist_head_t));
48 slist_init(p_head);
49
50 my_list_add(p_head, 1);
51 my_list_add(p_head, 2);
52 my_list_add(p_head, 3);
53 slist_foreach(p_head, list_node_process, NULL); //打印出所有結點的數據信息
54 my_list_del(p_head, 1);
55 my_list_del(p_head, 2);
56 my_list_del(p_head, 3);
57 free(p_head);
58 return 0;
59 }
如果按照int型數據的示例,使用鏈表管理學生記錄,則需要在學生記錄中添加一個鏈表結點數據。比如:
typedef struct _student{
slist_node_t node; //包含鏈表結點
char name[10]; //姓名為字符串
char sex; //性別為字符型
float height, weight; //身高、體重為實型
}student_t;雖然這樣定義使得學生信息可以使用鏈表來管理,但卻存在一個很嚴重的問題,因為修改了學生記錄類型的定義,就會影響所有使用該記錄結構體類型的程序模塊。在實際的應用上,學生記錄可以用鏈表管理,也可以用數組管理,當使用數組管理時,則又要重新修改學生記錄的類型。而node僅僅是鏈表的結點,與學生記錄沒有任何關系。不能將node直接放在學生記錄結構體中,應該使它們分離。基于此,需要定義一個新的結構體類型,將學生記錄和node關聯起來,使得可以用鏈表來管理學生記錄。比如:
typedef struct _slist_student{
slist_node_t node; //包含鏈表結點
student_t student; //學生記錄
}slist_student_t;使用范例詳見程序清單3.32。
程序清單3.32綜合程序范例
1 #include
2 #include "slist.h"
3 #include
4
5 typedef struct _student{
6 char name[10]; //姓名為字符串
7 char sex; //性別為字符型
8 float height, weight; //身高、體重為實型
9 }student_t;
10
11 typedef struct _slist_student{
12 slist_node_t node; //包含鏈表結點
13 student_t student; //學生記錄
14 }slist_student_t;
15
16 int student_info_read (student_t *p_student) //讀取學生記錄,隨機產生,僅供測試
17 {
18 int i;
19
20 for (i = 0; i < 9; i++) {???????????????????? ?????? //?隨機名字,由'a' ~ 'z'組成
21 p_student->name[i] = (rand() % ('z' - 'a')) + 'a';
22 }
23 p_student->name[i]= ' 主站蜘蛛池模板: 新版天堂资源中文8在线 | 福利一区二区在线观看 | 色吧五月天 | 黄视频在线观看网站 | 性做久久久久久网站 | 久久精品亚洲一区二区三区浴池 | 分分精品| 欧美日韩一区视频 | 国产中日韩一区二区三区 | 成人免费视频一区 | 色图插插插 | 免费h视频在线观看 | 一级毛片免费网站 | 天天插狠狠干 | 日本aaaaa | 日本一本在线视频 | 三级黄色在线视频中文 | 韩国三级观影久久 | 色播图片 | 男人日女人免费视频 | 天天碰免费视频 | 国产精品久久婷婷六月丁香 | 欧美在线观看一区二区三 | 五月亭亭激情五月 | 在线高清国产 | 天天做天天爱夜夜大爽完整 | 成人综合激情 | 欧美黄色片视频 | 亚洲伦理一区 | 欧美奇米 | 四虎1515hh永久久免费 | 天天舔天天射天天操 | 国产三级精品在线观看 | 玖玖玖精品视频免费播放 | 手机在线你懂得 | 无遮挡很污很爽很黄的网站 | 色播在线永久免费视频网站 | 激情伦成人综合小说 | 亚洲综合黄色 | 在线免费日韩 | 免费人成网站线观看合集 |