Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/58.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C-链表递归反转:为什么使用双指针?_C_Recursion_Linked List_Reverse - Fatal编程技术网

C-链表递归反转:为什么使用双指针?

C-链表递归反转:为什么使用双指针?,c,recursion,linked-list,reverse,C,Recursion,Linked List,Reverse,我理解struct Node*head是全局变量时的逻辑 然而,当我使用struct Node*head作为main()中的局部变量进行反向操作时,我必须使用双指针和head的指针,我不知道必须将它们精确放置在何处 void Reverse(struct Node *Head, struct Node **headpointer) { struct Node *first; // when the list is empty if (Head == NULL)

我理解
struct Node*head
是全局变量时的逻辑

然而,当我使用
struct Node*head
作为
main()
中的局部变量进行反向操作时,我必须使用双指针和
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
Reverse(first->next,headpointer)
,那么
first->next
值是否也等于指向
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
变量是指向要反转的列表头的指针,该函数将新反转的列表头存储到
*headpointer
中。之所以这样做,是因为
Head
只是传递给
Reverse()
函数的指针的副本,因此更新其值不会更改原始指针;这就是为什么要使用单独的双指针变量

为什么我不能直接使用
Head=first

Head
是传递给函数的指针的副本。更新
指针不会更新传入的原始列表指针。另外,正如我之前所说,
相同,因此这样的作业没有任何作用

另外,如果递归函数调用前面的
first=Head
Reverse(first->next,headpointer)
,那么
first->next
值是否也等于指向
first
节点的
Head


first->next
(或
Head->next
)只是列表中的下一个节点。函数调用的目的是首先反转列表的其余部分,然后在列表的末尾放置<代码>头<代码>(或<代码>第一/代码>)(这是最后两行所做的).< /p> 让我们考虑函数签名是这样的:

void Reverse(结构节点*头,结构节点*头指针){
你这样称呼它

反向(myList);
myList
只是节点的地址,例如
0x1234
。因此它相当于:

反向(0x1234);
地址被复制到一个新变量
headpointer
。当我们修改
headpointer
时,我们是在修改一个局部变量,而不是我们传递的
myList

就像我们这样做:

struct Node*myList=0x1234;
结构节点*headpointer=myList;
headpointer=0xABCD;
//此时,myList仍然是0x1234
因此,在函数返回后,
myList
仍然等于
0x1234
。这不是我们想要的,因为is现在应该指向最后一个节点

那么,我们如何允许函数修改myList呢?我们必须告诉函数“嘿,这是您必须写入的地址”

在C中,为了获取某物的地址,我们使用“&”运算符:

反向(&myList);
我们必须相应地更改函数的签名:

void Reverse(结构节点*头,结构节点**头指针){
现在,
headpointer
是指向
节点
的地址。或者,正如我们在C中所说,是指向
节点
的指针

下面是最后一个有助于理解的示例

struct Node*myList=0x1234;
结构节点**headpointer=&myList;//现在headpointer指向myList
*headpointer=0xABCD;
//此时,我的列表仍然是0xABCD
//我们没有更改headpointer,但是headpointer指向的东西!
必须能够返回更新后的
head
节点,但这可以通过返回值来完成,而不是使用双指针

此外,我认为需要向函数传递一个指向前一个节点的指针,以便它可以将其设置为新的
next
指针

因此,我认为您的函数需要三个参数(例如,
cur
prev
headpointer

如果我们返回
head
指针,我们可以将其减少为两个


这里有一个版本可以做到这一点

我尽可能多地对它进行注释,添加了debug
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;
}

以下是启用调试
printf
的程序输出:

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

您需要一个递归函数,但是您是否受限于它的给定签名?也就是说,您可以使用不同的参数和/或返回值吗?使用全局变量作为头指针会使链接列表几乎毫无用处。您只能有一个列表。如果将
head=first;
设置为
first
,将实现什么
Head
,则赋值不起任何作用,如果没有,则赋值会导致UB。当您将指针传递给函数时,函数会收到一个指针副本,该指针的地址与函数的值非常不同,但指针指向的地址与其值相同。当您反转列表时,必须更改Head指针的地址,使其不起作用ow将最后一个节点作为其值。此时,您有两个选项(1)将反向函数的返回类型设置为指针
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