递归的应用

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);
    }
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值