簡體   English   中英

C - 鏈表反向遞歸:為什么使用雙指針?

[英]C - linked list reverse recursively: why use double pointer?

我理解struct Node *head是全局變量時的邏輯。

但是,當我在main()中使用struct Node *head作為局部變量進行反向操作時,我必須使用雙指針和Head的指針,而且我不明白我必須將它們放在哪里。

void Reverse(struct Node *Head, struct Node **headpointer) {
    struct Node *first;
    // when the list is empty
    if (Head == NULL)
        return;

    first = Head;
    // when there is one node left
    if (first->next == NULL) {
        *headpointer = first;
        return;
    }

    Reverse(first->next, headpointer);

    first->next->next = first;
    first->next = NULL;
}

我不清楚為什么我必須使用...

first = Head;
*headpointer = first;

為什么我不能只使用

Head = first? 

另外,如果first = Head line 在遞歸 function call Reverse(first->next, headpointer)first->next值不也等於指向第first節點的Head嗎?

是否有任何好的邏輯圖/圖片/解釋/示例可以解釋這種差異?

我不清楚為什么我必須使用... first = Head

實際上,此代碼中根本不需要first變量。 它只是Head的副本,因此為了簡化您可以將變量替換為Head 沒有它,一切都會一樣:

if (Head == NULL)
  return;

if (Head->next == NULL) {
  *headpointer = Head;
  return;
}

Reverse(Head->next, headpointer);

Head->next->next = Head;
Head->next = NULL;

我不清楚為什么我必須使用... *headpointer = Head

Head變量是指向要反轉的列表頭部的指針,function 將新反轉的列表的頭部存儲到*headpointer中。 這樣做是因為Head只是傳遞給Reverse() function 的指針的副本,因此更新其值不會更改原始指針; 這就是使用單獨的雙指針變量的原因。

為什么我不能只使用Head = first

Head是傳遞給 function 的指針的副本。 更新Head指針不會更新您傳入的原始列表指針。另外,正如我之前所說, Headfirst相同,因此這樣的賦值沒有任何作用。

另外,如果first = Head line 在遞歸 function call Reverse(first->next, headpointer)first->next值不也等於指向第first節點的Head嗎?

first->next (或Head->next )只是列表中的下一個節點。 function 調用的目的是先反轉列表的 rest,然后將Head (或first )放在列表的末尾(這是最后兩行所做的)。

讓我們考慮 function 簽名是這樣的:

void Reverse(struct Node* Head, struct Node* headpointer) {

你這樣稱呼它

Reverse(myList);

myList只是一個節點的地址,例如0x1234 所以它相當於做:

Reverse(0x1234);

地址被復制到一個新的變量headpointer 當我們修改headpointer時,我們正在修改一個局部變量,而不是我們傳遞的myList

就像我們這樣做:

struct Node* myList = 0x1234;
struct Node* headpointer = myList;
headpointer = 0xABCD;
// at this point myList is still 0x1234

所以 function 返回后, myList仍然等於0x1234 這不是我們想要的,因為 is 現在應該指向最后一個節點。

那么我們如何允許 function 修改myList呢? 我們必須告訴 function “嘿,這是你必須寫信的地址”。

在 C 中,為了獲取我們使用“&”運算符的地址:

Reverse(&myList);

我們必須相應地更改 function 的簽名:

void Reverse(struct Node* Head, struct Node** headpointer) {

現在headpointer是指向Node的地址的地址。 或者,正如我們在 C 中所說的,一個指向Node的指針。

這是一個可以幫助理解的最后一個例子。

struct Node* myList = 0x1234;
struct Node** headpointer = &myList; // now headpointer points to myList
*headpointer = 0xABCD;
// at this point myList is still 0xABCD
// we haven't changed headpointer, but the thing headpointer is pointing to!

必須能夠返回更新后的head節點,但這可以通過返回值代替使用雙指針來完成。

另外,我相信 function需要傳遞一個指向前一個節點的指針,以便它可以將其設置為next指針。

所以,我認為你的 function 需要三個arguments (例如) curprevheadpointer

如果我們返回head指針,我們可以將它減少到兩個


這是一個可以做到這一點的版本。

我試圖盡可能多地注釋它。 我添加了調試printf和一些交叉檢查 [我曾經自己調試過]。

如果需要一個雙指針,這個例子可以適應而不會有太多麻煩。

// revlist -- reverse singly linked list recursively

#include <stdio.h>
#include <stdlib.h>

typedef struct node {
    struct node *next;
    int data;
#ifdef CHECK
    int visited;                    // 1=node visited (check for looped list)
#endif
} Node;

#ifdef DEBUG
int dbglvl;                         // nesting level -- debug only
#endif

int
prt(Node *node)
{
    int data;

    if (node != NULL)
        data = node->data;
    else
        data = -1;

    return data;
}

#ifdef DEBUG
#define dbgprt(_fmt ...) \
    do { \
        printf("DBG/%d: ",dbglvl); \
        printf(_fmt); \
    } while (0)
#else
#define dbgprt(_fmt ...) \
    do { } while (0)
#endif

// reverse -- recursively reverse list
// RETURNS: pointer to head of reversed list
Node *
reverse(Node *cur,Node *prev)
// cur -- pointer to current node
// prev -- pointer to previous node
{
    Node *next;
    Node *head = NULL;

    do {
        // empty list
        if (cur == NULL)
            break;

        next = cur->next;

        dbgprt("BEG cur=%d prev=%d next=%d\n",
            prt(cur),prt(prev),prt(next));

        // at end of list -- set new head from tail node
        if (next == NULL) {
            head = cur;
            head->next = prev;
            dbgprt("SETHEAD head=%d head->next=%d\n",
                prt(head),prt(head->next));
            break;
        }

#ifdef DEBUG
        ++dbglvl;
#endif
        // process the next node and give the current node as the previous one
        head = reverse(next,cur);
#ifdef DEBUG
        --dbglvl;
#endif

        // set the link pointer to the previous node
        cur->next = prev;
        dbgprt("POST cur->next=%d\n",prt(cur->next));
    } while (0);

    dbgprt("EXIT head=%d\n",prt(head));

    return head;
}

// addnode -- add node to tail of list
Node *
addnode(Node *head,int data)
{
    Node *newnode = malloc(sizeof(*newnode));
    Node *cur;
    Node *prev;

    newnode->next = NULL;
    newnode->data = data;
#ifdef CHECK
    newnode->visited = 0;
#endif

    // find the tail of the list
    prev = NULL;
    for (cur = head;  cur != NULL;  cur = cur->next)
        prev = cur;

    // add new node to list
    if (prev != NULL)
        prev->next = newnode;
    else
        head = newnode;

    return head;
}

// list_print -- print list
void
list_print(Node *head,const char *reason)
{
    Node *cur;

    printf("%s:",reason);

    for (cur = head;  cur != NULL;  cur = cur->next) {
        printf(" %d",cur->data);
#ifdef CHECK
        if (cur->visited) {
            printf(" DUP\n");
            exit(1);
        }
        cur->visited = 1;
#endif
    }

    printf("\n");

#ifdef CHECK
    for (cur = head;  cur != NULL;  cur = cur->next)
        cur->visited = 0;
#endif
}

// list_destroy -- destroy the list
void
list_destroy(Node *cur)
{
    Node *next;

    for (;  cur != NULL;  cur = cur->next) {
        next = cur->next;
        free(cur);
    }
}

// dotest -- test reverse function
void
dotest(int max)
{
    Node *head = NULL;

#ifdef DEBUG
    dbglvl = 0;
#endif

    // create a list
    for (int idx = 1;  idx <= max;  ++idx)
        head = addnode(head,idx);

    // show the original list
    list_print(head,"Forward");

    // reverse the list
    head = reverse(head,NULL);

    // show the reversed list
    list_print(head,"Reverse");

    // destroy the test list
    list_destroy(head);
}

int
main(void)
{

    dotest(8);

    return 0;
}

這是程序 output:

Forward: 1 2 3 4 5 6 7 8
Reverse: 8 7 6 5 4 3 2 1

這是啟用了調試printf的程序 output:

Forward: 1 2 3 4 5 6 7 8
DBG/0: BEG cur=1 prev=-1 next=2
DBG/1: BEG cur=2 prev=1 next=3
DBG/2: BEG cur=3 prev=2 next=4
DBG/3: BEG cur=4 prev=3 next=5
DBG/4: BEG cur=5 prev=4 next=6
DBG/5: BEG cur=6 prev=5 next=7
DBG/6: BEG cur=7 prev=6 next=8
DBG/7: BEG cur=8 prev=7 next=-1
DBG/7: SETHEAD head=8 head->next=7
DBG/7: EXIT head=8
DBG/6: POST cur->next=6
DBG/6: EXIT head=8
DBG/5: POST cur->next=5
DBG/5: EXIT head=8
DBG/4: POST cur->next=4
DBG/4: EXIT head=8
DBG/3: POST cur->next=3
DBG/3: EXIT head=8
DBG/2: POST cur->next=2
DBG/2: EXIT head=8
DBG/1: POST cur->next=1
DBG/1: EXIT head=8
DBG/0: POST cur->next=-1
DBG/0: EXIT head=8
Reverse: 8 7 6 5 4 3 2 1

暫無
暫無

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

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