指標與引用刪除單鏈表結點的區別
轉載於https://blog.csdn.net/zhongshijunacm/article/details/46592799 及https://blog.csdn.net/plm199513100/article/details/78172029
/ *
問題描述:遞迴刪除連結串列中的X
說明:。此處要注意的是,在遞迴刪除X的時候,並中間沒有產生斷鏈因為函式傳遞的的英文引用
關於引用,這次又詳細的查了一下特說明如下:
其實引用,在教材上被解釋成別名。就是給變數另起一個名字。從本質上說,其實並沒有引用這回事,引用的內部實現過程還是利用指標來實現的。
比如說:int i; int&j = i; 然後我們就可以說j是我的引用了,在編譯器編譯的時候,一般來說,它會把上面的第二條語句翻譯成這樣,
int const * j =&i;然後在程式中所有用到Ĵ的地方都用*∫來代替。只是對於我們來說,這個過程被遮蔽了。我們不用再去管Ĵ這個指標,使用時直接當成和我相似的變數就行。個人覺得普通的變數用引用沒什麼必要,在C ++中更多的當成指標的引用來傳遞引數,使得在函式內部可以改變指標所指的物件,這個在連結串列中用的較多。其實在也可以用ç語言的二重指標實現,可能是因為二重指標有點煩,易出錯,所以C ++才規定了引用這個東西,把引用給包裝了起來。在深入一點,JAVA中連指標也沒有了,應該是在C ++的基礎上用了更高層次的封裝了吧。
* /
問題的由來:
當你第一次實現用遞迴實現連結串列刪除功能的時候,是否有一絲絲的考慮過。這個問題呢?為什麼對於非遞迴版本的刪除必須要知道當前要刪除節點的前驅,而需要對其前驅節點的next域指標進行修改。而遞迴刪除卻不需要呢?難道這樣不會造成連結串列的斷鏈嗎?
好了。我們開始抽象出我們今天要解決的問題。
問題一:
遞迴實現連結串列節點的刪除和非遞迴刪除的區別是什麼?
問題二:
為什麼在使用遞迴刪除的時候連結串列不會斷鏈?
先給個程式碼,好根據程式碼模擬,不會空想。
函式的遞迴模型:
終止條件: f(L,x) = 不做任何事 若L為空表
遞迴主體: f(L,x) = 刪除*L結點;f(L->next,x); 若L->data == x
f(L,x) = f(L->next,x); 其他情況
/*
用遞迴實現對沒有頭結點的連結串列實現刪除給定數字
輸出最終結果
*/
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
typedef int ElemType;
const int MaxSize = 100;
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
//遞迴刪除連結串列節點函式
void Del_X_3(LinkList &L,ElemType x) {
LNode *p;
if(L == NULL) {
return;
}
if(L->data == x) {
p = L;
L = L->next;
free(p);
Del_X_3(L,x);
//printf("if2--> %d\n",L->data);
} else {
Del_X_3(L->next,x);
//printf("else--> %d\n",L->data);
}
//printf("--->%d\n",L->data);
}
int main(int argc,char **agrv) {
int n;
while(~scanf("%d",&n)) {
int x;
LinkList L = NULL;
LNode *s, *r = L;
for(int i = 0;i < n;++i) {
scanf("%d",&x);
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
if(L == NULL) L = s;
else r->next = s;
r = s;
//printf("-->%u\n",r);
}
r->next = NULL;
printf("Please enter you want to delete number: ");
scanf("%d",&x);
LNode *p = L;
while(p) {
printf("%ox ",p);
p = p->next;
}
puts("");
Del_X_3(L,x);
// //test
p = L;
while(p) {
printf("%ox %d\n",p,p->data);
p = p->next;
}
puts("");
}
return 0;
}
先解決問題一:
對於非遞迴版本的刪除連結串列結點,必須要知道其前驅結點。假設當前要刪除的結點為p,前驅結點為q。修改程式碼如下:q->next = p->next;而從上面的程式碼可以看出,對於遞迴版本的程式並不需要特別的知道其前驅結點。
再解決問題二:
首先,我們要先明確一個問題。就是上面給出的程式是用引用的。這說明了函式是傳址呼叫。這就是當刪除一個結點時候,不用需要知道前驅結點也可以的根本所在。
給個例子模擬一下你就知道了:
3 //輸入三個數
3 4 5
4 //刪除4
模擬函式呼叫過程:
初始連結串列邏輯關係:3 --> 4 --> 5
1、從3結點開始: f(&L,4) ----> 這時候明顯不是要刪除的數。
所以呼叫else部分。
L1->next引用傳址。(當前的L表示的是結點3)
2、4結點開始: f(&L,4) -----> 這時候發現4就是要刪除的數。
呼叫if2部分
處理程式碼為:L2 = L2->next(發現問題嗎?)
(L1和L2同表示L,為了好說明特別加以區別加了下標。)
其實,L2 == L1->next(即L1->next為結點3的next域)
而執行L2= L2->next現在就相當於把3的next域指向了5 的指標域。(L1->next = L2->next。所以,我們在這個 刪除的過程中還是隱含的知道了要刪除結點的前驅結點)
即,現在的邏輯關係變為:3->5
後面的就都一樣了,就不在詳說了。程式一直執行到if1 條件滿足為止,然後開始遞迴返回值。最後終止。
而這個過程是傳址的。所以,這回影響最終的結果。
好了。兩個問題都完美解決了。雖然,問題不是什麼難題。但是,如果對語言和遞迴沒有深刻的掌握還是一時難以理解的。