1.递归作为一种算法在程序设计语言中广泛应用,是指函数/过程/子程序在运行过程中直接或间接调用自身而产生的重入现象。
2.递归算法一般用于解决三种问题:
1)数据的定义是按递归定义的。( Fibonacci(斐波那契)函数)。
2)问题解决按递归算法实现。(回溯)
3)数据的结构形式是按递归定义的。(数的便利,图的搜索)
递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。
在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。在实际编程中尤其要注意栈溢出问题。
借助递归方法,我们可以把一个相对复杂的问题转化为一个与原问题相似的规模较小的问题来求解,递归方法只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。但在带来便捷的同时,也会有一些缺点,也即:通常用递归方法的运行效率不高。
/**
* 计算二进制中1的个数
* N为奇数,二进制中1的个数为N/2
* @author 伟1
*
*/
public class Ditui {
/*
* num=13
* 1.getNum(13/2=6)+1;
* 2.getNum(6/2=3)+1;
* 3.getNum(3/2=1)+1+1;
* 4.getNumm(1)+1+1;
*/
public static int getNum(int num) {
if (num==1) {
return 1;
}else if (0==num%2) {
return getNum(num/2);
}else {
return getNum(num/2)+1;
}
}
public static void main(String[] args) {
System.out.println(new Ditui().getNum(13));
}
}
二叉树遍历
在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。
一颗简单的二叉树
二叉树的遍历分为三种:前(先)序、中序、后序遍历。
设L、D、R分别表示二叉树的左子树、根结点和遍历右子树,则先(根)序遍历二叉树的顺序是DLR,中(根)序遍历二叉树的顺序是LDR,后(根)序遍历二叉树的顺序是LRD。还有按层遍历二叉树。这些方法的时间复杂度都是O(n),n为结点个数。
假设我们有一个包含值的value和指向两个子结点的left和right的树结点结构。我们可以写出这样的过程:
先序遍历(递归实现):
1 2 3 4 |
visit(node) print node.value if node.left != null then visit(node.left) if node.right != null then visit(node.right) |
中序遍历(递归实现):
1 2 3 4 |
visit(node) if node.left != null then visit(node.left) print node.value if node.right != null then visit(node.right) |
后序遍历(递归实现):
1 2 3 4 |
visit(node) if node.left != null then visit(node.left) if node.right != null then visit(node.right) print node.value |
字符串全排列
问题:
写一个函数返回一个串的所有排列。
解析:
对于一个长度为n的串,它的全排列共有A(n, n)=n!种。这个问题也是一个递归的问题, 不过我们可以用不同的思路去理解它。为了方便讲解,假设我们要考察的串是”abc”, 递归函数名叫permu。
思路一:
我们可以把串“abc”中的第0个字符a取出来,然后递归调用permu计算剩余的串“bc” 的排列,得到{bc, cb}。然后再将字符a插入这两个串中的任何一个空位(插空法), 得到最终所有的排列。比如,a插入串bc的所有(3个)空位,得到{abc,bac,bca}。 递归的终止条件是什么呢?当一个串为空,就无法再取出其中的第0个字符了, 所以此时返回一个空的排列。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
typedef vector<string> vs; vs permu(string s){ vs result; if(s == ""){ result.push_back(""); return result; } string c = s.substr(0, 1); vs res = permu(s.substr(1)); for(int i=0; i<res.size(); ++i){ string t = res[i]; for(int j=0; j<=t.length(); ++j){ string u = t; u.insert(j, c); result.push_back(u); } } return result; //调用result的拷贝构造函数,返回它的一份copy,然后这个局部变量销毁(与基本类型一样) } |
思路二:
我们还可以用另一种思路来递归解这个问题。还是针对串“abc”, 我依次取出这个串中的每个字符,然后调用permu去计算剩余串的排列。 然后只需要把取出的字符加到剩余串排列的每个字符前即可。对于这个例子, 程序先取出a,然后计算剩余串的排列得到{bc,cb},然后把a加到它们的前面,得到 {abc,acb};接着取出b,计算剩余串的排列得到{ac,ca},然后把b加到它们前面, 得到{bac,bca};后面的同理。最后就可以得到“abc”的全序列。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
vs permu1(string s){ vs result; if(s == ""){ result.push_back(""); return result; } for(int i=0; i<s.length(); ++i){ string c = s.substr(i, 1); string t = s; vs res = permu1(t.erase(i, 1)); for(int j=0; j<res.size(); ++j){ result.push_back(c + res[j]); } } return result; } |