1, Hanoi塔问题
Hanoi塔问题是,有A,B,C三根柱子,每根柱子都可以穿若干盘子。现在A柱上有4个盘子,从上到下越来越大。需要将全部盘子利用B作为中间移动到C柱上。规则是每次只能移动一个盘子,且盘子只能放到比自己大的盘子上。现在希望打印出移动的每一步的过程。
要用递归解决这个问题,就要思考盘子在最后阶段处于的状态:
1,想办法将n-1个盘子从A移动到B上,使用C为中间柱。
2,将第n个盘子从A移动到C上,只需要一步。
3,想办法将n-1个盘子从B移动到C上,使用A为中间柱。
下面是代码。通过观察可以发现,代码完全反应了上面的分析结果。
public class Testing {
static int dish = 3;
public static void main(String[] args) {
Testing.doTower(dish, "A", "B", "C");
}
private static void doTower(int topN, String from, String inter, String to) {
if (topN == 1) {
//如果A柱只有一个盘子,那么直接从A柱移动到C柱。
System.out.println("Move dish " + topN + " from tower " + from + " to tower " + to);
} else {
//将A柱的n-1个盘子想办法通过C柱移动到B柱。
doTower(topN - 1, from, to, inter);
//然后将第n个盘子从A柱移动到C柱。这里直接调用doTower(1, "A", "B", "C")也是一样的。
System.out.println("Move dish " + topN + " from tower " + from + " to tower " + to);
//然后想办法把B柱上的n-1个盘子通过A柱移动到C柱上。
doTower(topN - 1, inter, from, to);
}
}
}
这里我们不可以说写个算法将A的所有盘子,通过B放到C上。因为这样递归就没有结束条件了。所以每次递归一定要是topN-1。
再来回顾一下解题的过程,设完成n层hanio的次数为H(n)。利用递归解决问题时,必须完成下列步骤:
1,必须找到H(n)和H(n-1)的关系,也就是所谓的递推公式。例如hanio塔问题的递推公式就是:
H(n) = H(n-1) + 1 + H(n-1)
如果一个问题无法找到递推公式,则不能利用递推来解决。
2,必须有结束条件。如hanio问题的结束条件就是当盘子为1个时,简单的从A移动到C就可以了。
2,乘方
计算x的y方。例如,计算2的8次方。通常的做法是用8个2相乘。
private static int power(int x, int y){
if (y == 1){
return x;
}
else{
x = x * power(x,y-1);
System.out.println("1"); //这种递归没有任何优势,从这句话可知,递归的运行了y-1次。和直接循环相乘没有区别。
return x;
}
}
其实我们在运算比如2的8次方时,完全可以首先计算2*2,然后利用递归加快这个过程:
普通算法:2*2*2*2*2*2*2*2 需要相乘8次。
改进算法:((2*2)^2)^2 仅需要相乘3次。也就是以2为底,8的对数。
private static int power(int x, int y){
if (y == 1){
return x;
}
else{
if (y%2 == 0){
x = power(x*x,y/2); //其实这里还可以理解成:2的8次方就等于4的4次方。
System.out.println("1");
return x;
}
else{
x = x*power(x,y-1); //当y是奇数时,还是需要用普通算法计算一次。之后y-1又是偶数了。
System.out.println("1"); //最终我们可以得知这种算法仅需要运行logY次。因此效率高的多。
return x;
}
}
}
我们甚至还可以用:
if(y%3 == 0){
System.out.println("b");
return power(x*x*x,y/3);
}
来进一步加快进程。
3,merge sort
merge排序的思想在于,将数组分成两个较小的有序数组,然后再将这两个有序数组合并在一起,就得到了排序后的整个数组。
那么如何得到两个较小的有序数组呢?就是将每个小组再划分为两个更小的有序数组,再merge。。。如此循环划分,直到数组中只剩下一个元素,即不需要再把组划分,也已经是有序数组了。
显而易见这里要用到递归了。
首先看看,没有递归的时候,如何将两个有序数组merge到一起:
public class mergeSort {
public static void main(String[] args) {
int[] a = {2,5,8,35,78,99,102,103,201};
int[] b = {5,7,17,32};
System.out.println(mergeSort(a,b) );
}
private static ArrayList mergeSort(int[] a, int[] b){
ArrayList c = new ArrayList();
int aInd=0;
int bInd=0;
int cInd=0;
while(aInd < a.length && bInd < b.length){
if (a[aInd] > b[bInd]){
c.add(cInd++, b[bInd++]);
}
else{
c.add(cInd++,a[aInd++]);
}
}
while(aInd == a.length && bInd < b.length){
c.add(cInd++,b[bInd++]);
}
while(bInd == b.length && aInd < a.length){
c.add(cInd++,a[aInd++]);
}
return c;
}
}
下面是merge sort的完整代码
public class mergeSort {
public static void main(String[] args) {
int[] initial = {5,2,7,8,102,45,36,22,201,43};
recSort(initial);
}
//recsort的任务就是将数组划分成2个更小的数组。
private static int[] recSort(int[] initial){
int[] begin;
int[] end;
if ( initial.length == 1 ){
return initial; //当数组被划分的最后只有一个元素时,就不需要再划分了。这就是基值条件。
}
else{
begin = Arrays.copyOfRange(initial, 0, initial.length/2); //begin数组代表数组的前半部分
end = Arrays.copyOfRange(initial, initial.length/2, initial.length);//end数组代表数组的后半部分
begin = recSort(begin); //让begin递归的被recsort划分。
end = recSort(end);
return mergeSort(begin, end);
//这句returen非常关键。当recsort循环到最内层的时候,mergeSort开始从栈里取出begin和end开始合并。
//同时合并的结果作为下一次mergeSort递归调用的输入参数。
//也就是,例如:上次合并的结果[2, 5] [7, 8, 102]这两个有序数组 成为了下次mergeSort的输入参数。
//这个例子有意思的地方是:我们使用recSort方法递归划分数组到最内层,使用mergeSort方法从最内层递归合并数组到最外层。从而这两个方法共同完成了递归调用。
}
}
//mergeSort方法和前面完全一致。关键在于如何调用。
private static int[] mergeSort(int[] a, int[] b){
int[] c = new int[a.length + b.length];
int aInd=0;
int bInd=0;
int cInd=0;
while(aInd < a.length && bInd < b.length){
if (a[aInd] > b[bInd]){
c[cInd++] = b[bInd++];
}
else{
c[cInd++] = a[aInd++];
}
}
while(aInd == a.length && bInd < b.length){
c[cInd++] = b[bInd++];
}
while(bInd == b.length && aInd < a.length){
c[cInd++] = a[aInd++];
}
System.out.println(Arrays.toString(c));
return c;
}
}
4,背包问题
背包问题有很多种形式,最简单的是:有5种重量的物体,1kg,2kg,5kg,10kg,20kg。现在将其中的一个或多个放入背包中,使背包的总重量为25kg。
一个物体只能出现一次。当找的一个可能的组合后返回reture,没有找到返回false。
背包问题属于树搜索问题,只能将所有的可能性都尝试一遍。代码比较复杂放到另外一个贴里了。
5,概率问题
从5个字母(A,B,C,D,E)中选择出3个有多少种组合。注意这里是组合而不是排列,因此ABC和ACB是一样的组合。
递归的解决方法就是找个一个问题的中间态,因此这里我们可以考虑:
从5个字母中选出3个字母的组合等于=由A开头的所有组合+不是由A开头的所有组合
也可以表示为:
(5,3) = (4,2) + (4,3)
也就是f(n,k) = f(n-1,k-1) + f(n-1,k)
而基值条件为:
(i,1) -- 当k等于1时,有i种可能。例如从3个字母中挑1个出来,就有3种可能的组合。
(i,j) -- 当i和j相等时,有1种可能。例如从3个字母中挑3个字母出来,就只有一种组合。
代码非常的简单:
public class Testing {
public static void main(String arg[]){
System.out.println(selector1(5,3));
}
public static int selector1(int i, int j){
if(j == 1){
return i;
}
if(i == j){
return 1;
}
return selector1(i-1,j-1)+selector1(i-1,j);
}
}