簡體   English   中英

使用遞歸錯誤反向鏈接列表

[英]reverse a linked list using recursion error

我了解遞歸,所以我嘗試編寫反向鏈接列表程序。 我已經寫了下面的函數,但是它說分段錯誤(核心轉儲)。

void reverse(){
    if (head -> next == NULL){
        return;
    }
    reverse (head -> next);
    struct node *q = (struct node*) malloc (sizeof(struct node));
    q = head -> next;
    q -> next = head;
    head -> next = NULL;
}

請有人指導我。 謝謝。

不應該反向爭論嗎? 並且請注意,您不能更改函數中的指針,而要進行持久更改。 也就是說,在C函數中,唯一持久的更改是使用* var = something的更改。

遞歸是通過實踐獲得的一種思維方式。 因此,祝賀您的嘗試。 這是不正確的,但不要灰心。

這是解決該問題的兩種方法。

您的目標是將其細分為自身的較小版本,再加上(希望能夠輕松,快速地進行計算)增量步驟,以將較小版本的解決方案變為完整的解決方案。 這是遞歸思維的本質。

首先嘗試:將列表視為頭元素加上“列表的其余部分”。 也就是說,

L = empty or
  = h . R

其中h是頭元素R是列表的其余部分,而dot是. 正在將新元素加入列表。 反轉此列表包括反轉R ,然后在末尾附加h

rev(L) = empty if L is empty
       = rev(R) . h otherwise

這是一個遞歸解決方案,因為我們可以遞歸調用反向函數來解決稍微小的R反轉問題,然后在h后面添加一些工作,從而為我們提供了完整的解決方案。

這個公式的問題在於,附加h比您想要的要昂貴。 因為我們有一個只有頭指針的單鏈接列表,所以很耗時:遍歷整個列表。 但這會很好。 在C中將是:

NODE *rev(NODE *head) {
  return head ? append(head, rev(head->next)) : NULL;
}

NODE *append(NODE *node, NODE *lst) {
  node->next = NULL;
  if (lst) {
    NODE *p;
    for (p = lst; p->next; p = p->next) /* skip */ ;
    p->next = node;
    return lst;
  }
  return node;
}

那么如何擺脫不良表現呢? 通常,問題的不同遞歸公式具有不同的效率。 因此,經常需要反復試驗。

下一步:考慮將列表分為兩個子列表的計算:L = HT,因此rev(L)= rev(T)+ rev(H)。 這里加號+是列表串聯。 關鍵是,如果我知道rev(H)並想在其頭部添加一個新元素,則要添加的元素是T中的第一個元素。如果這看起來很模糊,則讓H = [a,b,c]和T = [d,e]。 然后,如果我已經知道rev(H)= [c,b,a]並想將下一個元素放在開頭,則我想要d,它是T的第一個元素。在我們的小記號中,您可以編寫此觀察值就是這樣:

rev(H + (d . T)) = rev(T) + ( d . rev(H) )

因此,這看起來非常好。 在這兩種情況下(獲取T的頭部並將其移至rev(H)的頭部),我只對列表的頭部感興趣,這對於訪問非常有效。

當然,如果T為空,則rev(H)= rev(L)。 這就是答案!

將其編寫為遞歸過程。

NODE *rev(NODE *t, NODE *rev_h) {
  if (t) {                // if t has some elements
    NODE *tail = t->next;   // save the tail of T
    t->next = rev_h;        // prepend the head to rev(H)
    return rev(tail, t);    // recur to solve the rest of the problem
  }
  return rev_h; // otherwise T is empty, so the answer is rev(H)
}

首先,我們對rev(H)一無所知,所以T是整個列表:

NODE *reversed_list = rev(list, NULL);

接下來要注意的是,該函數是尾遞歸的:遞歸調用是在函數返回之前執行的。 很好! 這意味着我們可以輕松地將其重寫為循環:

NODE *rev(NODE *t, NODE *rev_h) {
recur:
  if (t) {                // if t has some elements
    NODE *tail = t->next;   // save the tail of T
    t->next = rev_h;        // prepend the head to rev(H)
    rev_h = t;              // "simulate" the recursive call
    t = tail;               //   by setting both args
    goto recur;             //   and going back to the start
  }
  return rev_h; // otherwise T is empty, so the answer is rev(H)
}

您始終可以通過尾遞歸調用來進行此轉換。 您應該認真考慮為什么這樣做。

現在, goto可以很容易地重寫為while循環,並且我們可以將rev_h為初始化為NULL的局部變量,因為這就是所有初始調用的作用:

NODE *rev(NODE *t) {
  NODE *rev_h = NULL;
  while (t) {             // while t has some elements
    NODE *tail = t->next;   // save the tail of T
    t->next = rev_h;        // prepend the head to rev(H)
    rev_h = t;              // "simulate" the recursive call
    t = tail;               //   by setting both args
  }
  return rev_h; // otherwise T is empty, so the answer is rev(H)
}

一個就地鏈表反向器,只需要少量恆定的空間!

看! 我們不必繪制有趣的方框圖和箭頭圖,也不必考慮指針。 通過仔細思考如何將問題細分為自身的較小實例(遞歸的本質),它“剛剛發生”。 這也是查看循環只是一種特殊的遞歸的好方法。 太好了

我假設您在.c文件中預定義了以下內容

typedef struct node node_t;
struct node {
    int some_data;
    node_t *next;
};

/* Your linked list here */
typedef struct {
    node_t *head;
    node_t *foot; /* to keep track of the last element */
} list_t;

在執行功能時,您犯了一些錯誤

  • 不提供任何輸入參數
  • 當程序不知道在哪里找到頭時訪問head-> next

因此,導致C中最令人沮喪的錯誤-分段錯誤!

相反,您應該嘗試以下操作:

void reverse(list_t *mylinkedlist){
    if (mylinkedlist->head->next == NULL) {
        return;
    }
    /* do something */
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM