利用二级指针删除单向链表
http://coolshell.cn/articles/8990.html
Linus舉了一個單向鏈表的例子,但給出的代碼太短了,一般的人很難搞明白這兩個代碼后面的含義。正好,有個編程愛好者閱讀了這段話,并給出了一個比較完整的代碼。他的話我就不翻譯了,下面給出代碼說明。
如果我們需要寫一個remove_if(link*, rm_cond_func*)的函數,也就是傳入一個單向鏈表,和一個自定義的是否刪除的函數,然后返回處理后的鏈接。
這個代碼不難,基本上所有的教科書都會提供下面的代碼示例,而這種寫法也是大公司的面試題標準模板:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | typedef struct node { ????struct node * next; ????.... } node; typedef bool (* remove_fn)(node const * v); // Remove all nodes from the supplied list for which the // supplied remove function returns true. // Returns the new head of the list. node * remove_if(node * head, remove_fn rm) { ????for (node * prev = NULL, * curr = head; curr != NULL; ) ????{ ????????node * const next = curr->next; ????????if (rm(curr)) ????????{ ????????????if (prev) ????????????????prev->next = next; ????????????else ????????????????head = next; ????????????free(curr); ????????} ????????else ????????????prev = curr; ????????curr = next; ????} ????return head; } |
這里remove_fn由調用查提供的一個是否刪除當前實體結點的函數指針,其會判斷刪除條件是否成立。這段代碼維護了兩個節點指針prev和curr,標準的教科書寫法——刪除當前結點時,需要一個previous的指針,并且還要這里還需要做一個邊界條件的判斷——curr是否為鏈表頭。于是,要刪除一個節點(不是表頭),只要將前一個節點的next指向當前節點的next指向的對象,即下一個節點(即:prev->next = curr->next),然后釋放當前節點。
但在Linus看來,這是不懂指針的人的做法。那么,什么是core low-level coding呢?那就是有效地利用二級指針,將其作為管理和操作鏈表的首要選項。代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void remove_if(node ** head, remove_fn rm) { ????for (node** curr = head; *curr; ) ????{ ????????node * entry = *curr; ????????if (rm(entry)) ????????{ ????????????*curr = entry->next; ????????????free(entry); ????????} ????????else ????????????curr = &entry->next; ????} } |
同上一段代碼有何改進呢?我們看到:不需要prev指針了,也不需要再去判斷是否為鏈表頭了,但是,curr變成了一個指向指針的指針。這正是這段程序的精妙之處。(注意,我所highlight的那三行代碼)
讓我們來人肉跑一下這個代碼,對于——
- 刪除節點是表頭的情況,輸入參數中傳入head的二級指針,在for循環里將其初始化curr,然后entry就是*head(*curr),我們馬上刪除它,那么第8行就等效于*head = (*head)->next,就是刪除表頭的實現。
- 刪除節點不是表頭的情況,對于上面的代碼,我們可以看到——
1)(第12行)如果不刪除當前結點 —— curr保存的是當前結點next指針的地址。
2)(第5行)?entry 保存了 *curr?——?這意味著在下一次循環:entry就是prev->next指針所指向的內存。
3)(第8行)刪除結點:*curr = entry->next; —— 于是:prev->next 指向了 entry -> next;
是不是很巧妙?我們可以只用一個二級指針來操作鏈表,對所有節點都一樣。
如果你對上面的代碼和描述理解上有困難的話,你可以看看下圖的示意:
#include <stdio.h> #include <iostream>using namespace std;struct node {int a;node *next; };void print(node* head) {for(;head!=NULL;head = head->next) {cout << head->a << " ";}cout << endl; }//this method is brief node* rm(node* head,int val) {node *pre = NULL;node *curr = head;for (;curr != NULL;) {node *next = curr->next;if(curr->a%2 == val) {if (pre == NULL) {head = next;}else {pre->next = next;}free(curr);}else {pre = curr;}curr = next;}return head; }void remove(node **head, int val) {//curr指向的是當前節點的指針的指針//對于中間節點而言,當前節點的指針和其前一個節點的next是一樣的,所以可以看做是前一個節點的next的指針for (node** curr = head; *curr != NULL;) {node *entry = *curr;//entry是當前節點的指針//1. curr是頭節點,那么*curr = entry->next,就是指head指向了下一個節點//2.如果curr是中間節點,則curr是前一個節點next的指針if (entry->a % 2 == val) {*curr = entry->next;free(entry);}else {curr = &(entry->next);}} }int main() {node *head = (node*)malloc(sizeof(node));head->a = 1;node *a = (node*)malloc(sizeof(node));a->a = 2;node *b = (node*)malloc(sizeof(node));b->a = 3;node *c = (node*)malloc(sizeof(node));c->a = 4;node *d = (node*)malloc(sizeof(node));d->a = 5;head->next = a;a->next = b;b->next = c;c->next = d;d->next = NULL;print(head);remove(&head,0);print(head);return 1; }
換言之,如果一個單向鏈表,next是 第一個 字段,我們可以用一個二級指針dummy引用一條鏈表上的所有節點。
struct node **dummy = &head->next;
一次解引用*dummy指向頭結點head
二次解引用**dummy指向head下一個節點
三次解引用*(*(*dummy))指向第三個節點
以此類推……
如果我們需要找到從head開始的第N個節點,那么對dummy進行N次解引用即可,當然現實工程中一般不會用到這么tricky的語法糖,但使用一個變量同時引用相鄰三個節點是很有用的;-)。
給還不明白的同學論證一下,見如下代碼
typedef struct list_s{
struct list_s * next;
int a;
int b;
} list_t;
main()
{
list_t lA, lB, lC, lD;
list_t ** phead = &lA.next;
lA.next = &lB;
lA.a = 11;
lA.b = 12;
lB.next = &lC;
lB.a = 21;
lB.b = 22;
lC.next = &lD;
lC.a = 31;
lC.b = 32;
lD.next = NULL;
lD.a = 41;
lD.b = 42;
printf(“%d\n”, ((list_t *)phead)->a);
printf(“%d\n”, (**(list_t**)phead).a);
printf(“%d\n”, (***(list_t***)phead).a);
printf(“%d\n”, (****(list_t****)phead).a);
printf(“%d\n”, ((list_t *)phead)->b);
printf(“%d\n”, (**(list_t**)phead).b);
printf(“%d\n”, (***(list_t***)phead).b);
printf(“%d\n”, (****(list_t****)phead).b);
}
gcc的結果是
11
21
31
41
12
22
32
42
總結
以上是生活随笔為你收集整理的利用二级指针删除单向链表的全部內容,希望文章能夠幫你解決所遇到的問題。