递归的应用

1, Hanoi塔问题

Hanoi塔问题是,有A,B,C三根柱子,每根柱子都可以穿若干盘子。现在A柱上有4个盘子,从上到下越来越大。需要将全部盘子利用B作为中间移动到C柱上。规则是每次只能移动一个盘子,且盘子只能放到比自己大的盘子上。现在希望打印出移动的每一步的过程。
首先仔细考虑这个问题后可知,每一步的移动都是固定的,不存在分支问题。要解决这个问题,必然要经过下列的步骤,否则A上最下面的那个盘子无法移动:
1,将n-1个盘子从A移动到B上,使用C为中间柱。
2,将最大的那个盘子从A移动到C上。
3,将n-1个盘子从B移动到C上,使用A为中间柱。
下面是代码。通过观察可以发现,代码完全反应了上面的分析结果。

public class Testing {

	static int dick = 3;

	public static void main(String[] args) {
		Testing.doTower(dick, 'A', 'B', 'C');
	}

	private static void doTower(int topN, char from, char inter, char to) {

		if (topN == 1) {
			System.out.println("Move disk " + topN + " from tower " + from + " to tower " + to);
		} else {

			doTower(topN - 1, from, to, inter);
			System.out.println("Move disk " + topN + " from tower " + from + " to tower " + to);
			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);
    }
}






你可能感兴趣的:(递归的应用)