递归概念的澄清

clarification on recursion concept

本文关键字:递归      更新时间:2023-10-16

如果我写下面所示的代码,我对递归有疑问

inorder(p){
if(p!=NULL){
inorder(p->link);  //step 1
cout<<p->info<<" "; //step 2
inorder(p->link);  //step 3
}
} 

这里,我的怀疑是,当执行步骤1时,控制返回到函数,然后再次执行步骤1,控制将返回到函数直到p为NULL,如果这是过程,那么控制将如何进入步骤2,即"cout"和步骤3。。。

我无法循环我大脑中的代码。。。

在步骤1中的"控制返回到同一功能"之前,CPU做了一个重要的步骤:它在inorder的代码中标记了一个位置,一旦inorder的"第二级"返回,它就需要重新开始执行(即步骤1之后的位置)。在那里,"第二级"在进入"第三级"、"第四级"等之前再次标记返回位置。最终,N级获得NULL,因此它立即返回。然后N-1级开始打印info,并继续第二次调用inorder。现在返回位置不同了——它就在步骤3之后。一旦N级结束,N-1级也结束,返回N-2级,然后返回N-3级,依此类推,直到第一级退出。

下面是一个示例树:

A
/   
B     C
/   
D     E

有序遍历的过程如下:

inorder(A)            -- step 1, level 1
inorder(B)          -- step 1, level 2
inorder(NULL)     -- returns
cout << B         -- step 2, level 2
inorder(NULL)     -- returns
return              -- level 2 returns 
cout << A           -- step 2, level 1
inorder(C)          -- step 3, level 2
inorder(D)        -- step 1, level 3
inorder(NULL)   -- returns
cout << D       -- step 2, level 3
inorder(NULL)   -- returns
return            -- level 3 returns
cout << C         -- step 2, level 2
inorder(E)        -- step 1, level 3
inorder(NULL)   -- returns
cout << E       -- step 2, level 3
inorder(NULL)   -- returns
return            -- level 3 returns
return              -- level 2 returns
return                -- level 1 returns

如果您想了解递归是如何工作的,您应该了解call stack。这是链接。希望这会有所帮助。。。

假设p指向Ap->link(也就是A.link)被称为q并指向B,而q->link(也就是说B.link)为NULL,则称其为r。

p      q      r
----->A----->B----->0

现在我们称之为inorder(p)

  1. p不为NULL,因此interder(p)调用interder(q)(步骤1)
  2. q不为NULL,因此interder(q)调用interder(r)(步骤1)
  3. r为NULL,因此interder(r)跳过块并将控制权返回给interder(q)
  4. interder(q)打印B.info(步骤2)
  5. interder(q)调用interder(r)(步骤3),后者再次将控制返回给interder(q),后者将控制返回到interder(p)
  6. interder(p)打印A.info(步骤2)
  7. interder(p)调用interder(q)(步骤3),它再次运行2-5(再次打印B.info),然后interder(p)将控制权返回给调用它的对象。

我第一次遇到递归是用阶乘函数。阶乘是数学中的一个基本函数,给定一个数N,它返回一个等于所有小于N的正整数的乘积的数。

考虑一下C++中的这段简单代码,它很好地演示了递归。

int fact(int x)
{
if(x==1)
return 1;
x = x*fact(x-1);
return x;
}

它输入一个整数,然后在递减整数后调用自己,直到整数为1,并返回x。注意行"return x;"直到该行结束之前才调用。

当对interder(p->link)进行第一次调用时,将其视为检查点。它将一直调用到达到NULL。然后执行第2行,第二次调用interder(p->link)也是如此。这就形成了一个树

interder(p->link)->coutinfo->interder(p->link)/^/^V/V/订单()->cout->interder()订单()->cout->订单().^/\.^/\.|…|。.||interder(p->link)//(p->link为NULL)interder(p->link)//[(p->link为NULL)

我认为您的代码是正确的。您正试图以以下方式打印出一个链接列表:

inorder(p){ 
if(p!=NULL)
{
inorder(p->link);   //step 1 to navigate to next link
cout<<p->info<<" "; //step 2 as main operation
inorder(p->link);   //step 3 to navigate to next link
}
} 

翻译成英文

inorder(p)
{
if p points to a linked list
{
print the list p points to;
print content of p;
print the list p points to again;
}
if p points to a null linked list     
{
do nothing;
}
}

然后,1->2->3的链表将输出((3)2(3))1(((3

正如您所看到的,只有在步骤1遇到空链表时,控制才会传递到步骤2。打印出信息后,将其传递到步骤3。

因此,

  • 当链接列表位于节点"3"时,

    步骤1遇到null并返回;

    步骤2打印出3;

    步骤3遇到null并返回;

  • 当节点"2"的链表

    步骤1执行它在节点"3"上所做的任何操作;//打印3

    步骤2打印出2;

    步骤3执行它在节点"3"上所做的任何操作;//打印3

  • 当节点"1"的链表

    步骤1执行它在节点"2"上所做的任何操作;//打印3 2 3

    步骤2打印出1;

    步骤3执行它在节点"2"上所做的任何操作;//打印3 2 3

考虑一个游戏,如果你在家里的不同地方留下了5条消息。每条信息都会引导您进入下一条。要赢得游戏,您必须找到所有5条消息并将它们返回给游戏主机。但是,您无法在找到消息时拾取这些消息。。。你必须记住它们的位置,并按照你找到它们的相反顺序把它们捡起来。

你会走到第一个项目,记下它在哪里,然后顺着线索走;记下你以后需要返回的地方,以便找到线索。接下来的4条线索你也可以这样做。

当你找到最后一条线索,所以不再存在时,你会开始向后看,回到你发现每条线索的地方,检索它们。

找到第一条线索就像你第一次调用"有序()"。从线索到第二条线索就像你对"有序者(p->link)"的呼唤。在找到所有线索后提取线索就像返回代码中的"步骤2"一样。

每次调用函数顺序时,数据(称为激活帧或帧)都会放在称为调用堆栈的数据结构上。每个帧都跟踪函数本地的数据,例如传递到函数中的参数、指向函数调用方激活帧的指针,以及重要的是,要在调用函数中执行的下一行代码的地址。因此,当你递归地调用interder时,你正在递归地将帧添加到这个堆栈中,每个帧都包含下一行代码的地址,当相应的函数完成并将控制权返回给函数调用方时,该代码应该被执行。

换言之,当您从称为步骤1的行调用interder,并且传入的参数p为NULL时,当函数退出时,调用函数将在称为步骤2的行开始执行。

查看维基百科页面http://en.wikipedia.org/wiki/Call_stack了解更多信息。

理解与调用堆栈相关的概念将有助于理解递归的情况。