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