博文的記錄源自閱讀著名的酷殼主頁:http://coolshell.cn/articles/8990.html
Coolshell圖文並茂說明了這個問題——我們在刪除鏈表的時候,常常需要記錄該結點在鏈表中是否有前趨prev。如果有,那么需要把prev->next指向該結點的next域,然后再刪除該結點,這樣才能保證鏈表不會因為刪除結點而“斷開”。像這樣:
void remove(list_node **ref_head, list_node *del) { if ((*ref_head) == NULL || del == NULL) return; list_node *cur = *ref_head, *prev = NULL; while (cur) { if (cur == del) { if (prev) { prev->next = cur->next; } else { *ref_head = cur->next; } delete cur; cur = NULL; break; } prev = cur; cur = cur->next; } }
那么,在遍歷鏈表的時候,這種方法需要對prev is null or not進行判斷,這在Linus Torvalds看來,是不聰明的做法,是不理解指針的表現。稍微想一下就知道,其實這樣做的根本原因也很明了,第一,需要知道被刪除的結點是否是鏈表的頭結點,第二,需要保證鏈表在刪除后不會“斷開”。
Linus的做法是用一個二級指針,去引用prev的next域。假設鏈表的結構名為list_node,那么這時要記錄的不是list_node *prev,而是list_node **ref_prev。首先,實現代碼是這樣的:
void remove(list_node **ref_head, list_node *del) { if ((*ref_head) == NULL || del == NULL) return; list_node *cur = *ref_head, **ref_prev = ref_head; while (cur) { if (cur == del) { *ref_prev = cur->next; delete cur; cur = NULL; break; } ref_prev = &(cur->next); cur = cur->next; } }
由代碼可以看出,在執行cur = cur->next之前,首先用一個二級指針去引用當前cur的next域,其實也就是引用了方法一中的prev->next,這個原理和函數中的“傳值”是一致的,就像swap函數一樣,如果函數原型如void swap(int a, int b),即pass-by-value,這時函數執行時的變量a,b只是傳入參數的一個副本,函數執行完畢對傳入的變量值沒有影響,要讓值改變,函數應該是傳入地址void swap(int &a, int &b);**ref_prev的道理也是如此,它不是記錄prev,而是引用了prev的next域,在必要的時候改變prev->next的指向,這保證了鏈表不“斷開”。其次,ref_prev的初始指向為鏈表的頭結點,如果刪除的結點是頭結點,*ref_prev = cur->next也只是簡單地把頭結點改為原先頭結點的next結點,不需要再判斷prev是否為NULL了。
今天看到了一道題目,大體意思是鏈表是排好序的,要你插入一個結點后,仍保持排序。稍微想想,似乎也是需要記錄prev,如果插入的結點在鏈表內(非頭結點也非尾結點),則要有這樣的操作:prev->next = new_node; new_node->next = cur; (這里假定prev->value <= new_node->value <= cur->value, 即鏈表是升序排序的)很明顯,這里的操作也可以用二級指針。
void insert_to_sorted_list(list_node **ref_head, int value, bool (*func)(int, int)) { if ((*ref_head) == NULL) return; list_node **ref_next = ref_head; list_node *cur = *ref_head; list_node *new_node = new list_node; new_node->value = value; new_node->next = NULL; while (cur) { if (func(value, cur->value)) { new_node->next = cur; *ref_next = new_node; return; } ref_next = &(cur->next); cur = cur->next; } *ref_next = new_node; }
這里考慮到題目未說明鏈表的排序方式(升序/降序),函數原型在參數里加入了一個函數指針。