1. 程式人生 > >指標與引用刪除單鏈表結點的區別

指標與引用刪除單鏈表結點的區別

轉載於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                          條件滿足為止,然後開始遞迴返回值。最後終止。

                         而這個過程是傳址的。所以,這回影響最終的結果。

好了。兩個問題都完美解決了。雖然,問題不是什麼難題。但是,如果對語言和遞迴沒有深刻的掌握還是一時難以理解的。