先贴出Eric Lippert在stackoverflow的递归写法:
http://stackoverflow.com/questions/9304469/how-to-perform-a-recursive-search/9314805#9314805
其中写到递归大致模板:
Result M(Problem prob)
{
if (<problem can be solved easily>)
return <easy solution>;
// The problem cannot be solved easily.
Problem smaller1 = <reduce problem to smaller problem>
Result result1 = M(smaller1);
Problem smaller2 = <reduce problem to smaller problem>
Result result2 = M(smaller2);
...
Result finalResult = <combine all results of smaller problem to solve large problem>
return finalResult;
}
举出二叉树深度的例子套用以上公式
int Depth(Tree tree)
{
// Start with the trivial case. Is the tree empty?
if (tree.IsEmpty) return 0;
// The tree is not empty.
// Reduce the problem to two smaller problems and solve them:
int depthLeft = Depth(tree.Left);
int depthRight = Depth(tree.Right);
// Now combine the two solutions to solve the larger problem.
return Math.Max(depthLeft, depthRight) + 1;
}
其又总结了要想写出递归所必备的三个条件:
- The problem has to get smaller every time you recurse.
- The problem has to eventually get so small that it can be solved without recursion
- The problem has to be solvable by breaking it down into a series of smaller problems, solving each one, and combining the results.
我自己的理解如下:
递归模板大致可以是:
T fun(T n)
{
if (边界条件)
return T的对象 //(这里不是看n,而是看函数的作用是什么,它要返回什么,该返回对象与返回类型T有关,也有可能是null)
else
{
//if(可能的其他边界条件)
T obj = fun(//更小的事件);//可能不止一个递归方程,如求左右子树时
return obj;
}
}
- 函数类型T 是void型
此时实际上述模板的两个return都可以保留写成:
T fun(T n)
{
if (//边界条件)
return;
else
{
//if(可能的其他边界条件)
fun(//更小的事件);//可能不止一个递归方程,如树时有左右子树,要两个
return;
}
}
当然,此时else中的return一般不写,因为,else中的语句运行完,隐式的就有return了,写出来只是显示地表达出递归的返回含义
- 边界条件
第一个边界条件就是考虑递归往下到最后一个结点时的情况,若在递归中(else语句中)还存在其他边界条件,可正面写,如
if (root != NULL)
fun();
而不写成
if (root == NULL) return //返回void或对象 ;
else fun();
这样在else语句中只会出现一个return语句
参考剑指offer面试题重建二叉树
BinaryTreeNode* ConstructCore
(
int* startPreorder, int* endPreorder,
int* startInorder, int* endInorder
)
{
// 前序遍历序列的第一个数字是根结点的值
int rootValue = startPreorder[0];
BinaryTreeNode* root = new BinaryTreeNode();
root->m_nValue = rootValue;
root->m_pLeft = root->m_pRight = NULL;
if(startPreorder == endPreorder)
{
if(startInorder == endInorder && *startPreorder == *startInorder)
return root; //最后一个结点endNode返回后就到上一级的函数了
else
throw std::exception("Invalid input.");
}
// 在中序遍历中找到根结点的值
int* rootInorder = startInorder;
while(rootInorder <= endInorder && *rootInorder != rootValue)
++ rootInorder;
if(rootInorder == endInorder && *rootInorder != rootValue)
throw std::exception("Invalid input.");
int leftLength = rootInorder - startInorder; //左子树的节点个数
int* leftPreorderEnd = startPreorder + leftLength;
if(leftLength > 0)
{
// 构建左子树
root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd,
startInorder, rootInorder - 1);
}
if(leftLength < endPreorder - startPreorder)
{
// 构建右子树
root->m_pRight = ConstructCore(leftPreorderEnd + 1, endPreorder,
rootInorder + 1, endInorder);
}
return root;
}
感想:
- 简单题目一般都可以用尾递归解决,复杂一些就不能把递归语句放在最后的return前面了(尾递归时,递归语句等价于结束前的循环语句)。
- 递归形式大部分都可以看成,一个结点()与剩余一整串的关系,然后该剩余一整串的处理采用递归方法。
递归语句写出后,其余语句的写法按照正常思维进行即可。
函数类型及作用是返回什么,递归语句相应的一定要返回什么,按格式写一般都不会出错,如:
fun(//更小的事件); //T是void
T obj = fun(//更小的事件); //T非void
举例: 从尾到头打印链表
void PrintListReversingly_Recursively(ListNode* pHead)
{
if (pHead == NULL)
return;
else
{
PrintListReversingly_Recursively(pHead->m_pNext);
printf("%d\t", pHead->m_nValue);
}
}
分析:
1. 其中 PrintListReversingly_Recursively(pHead->m_pNext);
这句可以看成已经完成了pHead->m_pNext
为首的后续结点的反转工作,原理在于递归的每个结点的PrintListReversingly_Recursively(pHead->m_pNext)
实际都包含了打印当前结点的下一个节点:printf("%d\t", pHead->m_nValue);
从而我们可以直接把递归语句看成就是完成了后续pHead->m_pNext
为首的链表所有工作。现在就把问题分为只剩链表头一个结点pHead
和剩余的一整串以pHead->m_pNext
为首的链表了,现在就可以用顺序的思维写他们的关系了,而他们的关系是逆序打印,故递归语句先写,pHead
后写。
2. 只有当前问题与更小规模问题的处理方法一致时才能使用递归,处理方法即使除递归外的语句,如printf("%d\t", pHead->m_nValue);
。