为了更好地理解递归,我试图计算每对()之间有多少个字符, 不计算其他()s中的字符。例如:
(abc(ab(abc)cd)(()ab))
会输出:
Level 3: 3
Level 2: 4
Level 3: 0
Level 2: 2
Level 1: 3
其中"等级"指()嵌套的级别。因此,第三级意味着字符在一对(3)内的一对(2)内的一对(1)内。
要做到这一点,我的猜测是,最简单的方法是对函数实现某种递归调用,如函数内部注释" recursiveParaCheck"。当我开始考虑复发关系时,我的方法是什么?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int recursiveParaCheck(char input[], int startPos, int level);
void main()
{
char input[] = "";
char notDone = 'Y';
do
{
//Read in input
printf("Please enter input: ");
scanf(" %s", input);
//Call Recursive Function to print out desired information
recursiveParaCheck(input, 1, 1);
printf("\n Would you like to try again? Y/N: ");
scanf(" %c", ¬Done);
notDone = toupper(notDone);
}while(notDone == 'Y');
}
int recursiveParaCheck(char input[], int startPos, int level)
{
int pos = startPos;
int total = 0;
do
{
if(input[pos] != '(' && input[pos] != ')')
{
++total;
}
//What is the base case?
if(BASE CASE)
{
//Do something?
}
//When do I need to make a recursive call?
if(SITUATION WHERE I MAKE RECURSIVE CALL)
{
//Do something?
}
++pos;
}while(pos < 1000000); // assuming my input will not be this long
}
答案 0 :(得分:0)
递归是一个很棒的编程工具。它提供了一种简单,有效的方法来解决各种问题。然而,通常很难看到如何递归地处理问题;它可能很难&#34;思考&#34;递归。编写一个递归程序也很容易,该程序要么运行时间太长,要么根本不能正确终止。在本文中,我们将介绍递归的基础知识,并希望帮助您开发或改进非常重要的编程技能。
什么是递归? 为了准确地说出递归是什么,我们首先要回答&#34;什么是递归?&#34;基本上,如果函数调用自身,则称该函数是递归的。
您可能认为这不是非常令人兴奋,但此函数演示了设计递归算法时的一些关键注意事项:
它处理一个简单的基本情况&#34;不使用递归。 在这个例子中,基本情况是&#34; HelloWorld(0)&#34 ;;如果要求函数打印零次,则它返回而不再生成&#34; HelloWorld&#34; s。 它避免了周期。 为什么要使用递归? 我们上面说明的问题很简单,我们编写的解决方案也有效,但我们可能最好只使用循环而不是通过递归来解决问题。递归倾向于发光的地方是问题稍微复杂一些。递归可以应用于几乎任何问题,但在某些情况下,您会发现它特别有用。在本文的其余部分,我们将讨论其中的一些场景,并且在此过程中,我们将讨论在使用递归时要记住的一些核心思想。
场景#1:层次结构,网络或图形 在算法讨论中,当我们谈论图表时,我们通常不会谈论显示变量之间关系的图表(例如您的TopCoder评级图表,其显示时间与您的评级之间的关系)。相反,我们通常会谈论以各种方式相互连接的事物,人或概念的网络。例如,路线图可以被视为显示城市以及它们如何通过道路连接的图表。图形可能很大,很复杂,并且难以以编程方式处理。它们在算法理论和算法竞赛中也很常见。幸运的是,使用递归可以更简单地处理图形。一种常见的图形类型是层次结构,其中一个例子是业务组织结构图:
名称管理员
贝蒂山姆
鲍勃莎莉
迪尔伯特纳丹
约瑟夫莎莉
内森维罗妮卡
莎莉维罗尼卡
山姆约瑟夫
苏珊鲍勃
维罗尼卡
在此图中,对象是人,图中的连接显示谁向公司中的人报告。我们图表上的一条向上线表示图表中较低的人向他们上方的人报告。在右边,我们看到这个结构如何在数据库中表示。对于每个员工,我们记录他们的姓名和经理的姓名(如果需要,我们可以根据这些信息重建整个层次结构 - 你知道怎么做?)。
现在假设我们的任务是编写一个看起来像&#34; countEmployeesUnder(employeeName)&#34;的函数。此功能旨在告诉我们有多少员工(直接或间接)向employeeName指定的人员报告。例如,假设我们正在调用&#34; countEmployeesUnder(&#39; Sally&#39;)&#34;找出有多少员工向莎莉汇报。
首先,它很简单,可以计算直接在她下面工作的人数。为此,我们遍历每个数据库记录,对于其经理为Sally的每个员工,我们递增一个计数器变量。实现这种方法,我们的函数将返回2:Bob和Joseph。这是一个开始,但我们也想计算像Susan或Betty这样的人,他们在层次结构中较低但是间接向Sally报告。这很尴尬,因为例如,在查看苏珊的个人记录时,并不是很清楚莎莉是如何参与的。
正如您可能已经猜到的,一个好的解决方案是使用递归。例如,当我们在数据库中遇到Bob的记录时,我们不会将计数器增加一个。相反,我们递增一(计算Bob),然后按报告给Bob的人数增加。我们如何知道有多少人向鲍勃报告?我们使用递归调用我们正在编写的函数:&#34; countEmployeesUnder(&#39; Bob&#39;)&#34;。这是这种方法的伪代码:
function countEmployeesUnder(employeeName)
{
declare variable counter
counter = 0
for each person in employeeDatabase
{
if(person.manager == employeeName)
{
counter = counter + 1
counter = counter + countEmployeesUnder(person.name)
}
}
return counter
}
如果情况不是很清楚,那么最好的办法是尝试在心理上逐行进行几次。请记住,每次进行递归调用时,都会获得所有局部变量的新副本。这意味着每次调用都会有一个单独的计数器副本。如果情况并非如此,那么当我们在函数开头将counter设置为零时,我们就会搞砸了。作为练习,请考虑如何更改函数以增加全局变量。提示:如果我们正在递增一个全局变量,我们的函数就不需要返回一个值。
使命陈述 编写递归算法时要考虑的一件非常重要的事情就是要清楚地了解我们的函数&#34;使命陈述。&#34;例如,在这种情况下,我假设一个人不应被视为向他或她自己报告。这意味着&#34; countEmployeesUnder(&#39; Betty&#39;)&#34;将返回零。因此,我们的职能任务声明可能是&#34;返回直接或间接向employeeName中指定的人报告的人数 - 不包括名为employeeName的人。&#34;
让我们思考一下必须改变的内容,以便让一个人确实算作向他或她自己报告。首先,我们需要做到这一点,以便如果没有人向某人报告,我们会返回一个而不是零。这很简单 - 我们只需更改行&#34; counter = 0&#34; to&#34; counter = 1&#34;在功能的开头。这是有道理的,因为我们的函数必须返回比之前更高的值1。拨打&#34; countEmployeesUnder(&#39; Betty&#39;)&#34;现在将返回1.
但是,我们必须非常小心。我们已经改变了我们的功能使命陈述,当使用递归时,这意味着要仔细查看我们如何递归使用呼叫。例如,&#34; countEmployeesUnder(&#39; Sam&#39;)&#34;现在给出一个错误的答案3.要了解原因,请遵循以下代码:首先,我们通过将计数器设置为1来计算Sam为1.然后当我们遇到Betty时,我们将她算作1。然后我们统计向贝蒂汇报的员工 - 现在也将返回1。
很明显,我们重复计算贝蒂;我们的职能&#34;使命陈述&#34;不再符合我们使用它的方式。我们需要摆脱线路&#34; counter = counter + 1&#34 ;,认识到递归呼叫现在将贝蒂视为&#34;向Betty&#34;报告的人。 (因此我们不需要在递归呼叫之前统计她)。随着我们的功能变得越来越复杂,模糊的任务陈述和#34;变得越来越明显。为了使递归工作,我们必须非常清楚地说明每个函数调用正在做什么,否则我们最终会遇到一些非常难以调试的错误。即使时间紧迫,通常也可以通过撰写评论详细说明函数应该做什么来开始。明确&#34;使命陈述&#34;意味着我们可以确信我们的递归调用将按照我们的预期运行,整个画面将正确组合在一起。