分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。
当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。这就是分治的基本思想。
找出伪币
给你一个装有1 6个硬币的袋子。1 6个硬币中有一个是伪造的,并且那个伪造的硬币比真的硬币要轻一些。你的任务是找出这个伪造的硬币。为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,利用这台仪器,可以知道两组硬币的重量是否相同。比较硬币1与硬币2的重量。假如硬币1比硬币2轻,则硬币1是伪造的;假如硬币2比硬币1轻,则硬币2是伪造的。这样就完成了任务。假如两硬币重量相等,则比较硬币3和硬币4。同样,假如有一个硬币轻一些,则寻找伪币的任务完成。假如两硬币重量相等,则继续比较硬币5和硬币6。按照这种方式,可以最多通过8次比较来判断伪币的存在并找出这一伪币。
另外一种方法就是利用分而治之方法。假如把1 6硬币的例子看成一个大的问题。第一步,把这一问题分成两个小问题。随机选择8个硬币作为第一组称为A组,剩下的8个硬币作为第二组称为B组。这样,就把1 6个硬币的问题分成两个8硬币的问题来解决。第二步,判断A和B组中是否有伪币。可以利用仪器来比较A组硬币和B组硬币的重量。假如两组硬币重量相等,则可以判断伪币不存在。假如两组硬币重量不相等,则存在伪币,并且可以判断它位于较轻的那一组硬币中。最后,在第三步中,用第二步的结果得出原先1 6个硬币问题的答案。若仅仅判断硬币是否存在,则第三步非常简单。无论A组还是B组中有伪币,都可以推断这1 6个硬币中存在伪币。因此,仅仅通过一次重量的比较,就可以判断伪币是否存在。
假设需要识别出这一伪币。把两个或三个硬币的情况作为不可再分的小问题。注意如果只有一个硬币,那么不能判断出它是否就是伪币。在一个小问题中,通过将一个硬币分别与其他两个硬币比较,最多比较两次就可以找到伪币。这样,1 6硬币的问题就被分为两个8硬币(A组和B组)的问题。通过比较这两组硬币的重量,可以判断伪币是否存在。如果没有伪币,则算法终止。否则,继续划分这两组硬币来寻找伪币。假设B是轻的那一组,因此再把它分成两组,每组有4个硬币。称其中一组为B1,另一组为B2。比较这两组,肯定有一组轻一些。如果B1轻,则伪币在B1中,再将B1又分成两组,每组有两个硬币,称其中一组为B1a,另一组为B1b。比较这两组,可以得到一个较轻的组。由于这个组只有两个硬币,因此不必再细分。比较组中两个硬币的重量,可以立即知道哪一个硬币轻一些。较轻的硬币就是所要找的伪币。
直接上代码:
1 package com.chenjun.packge1; 2 3 public class Algorithm { 4 private static int a[] = new int[10]; 5 static{ 6 for(int i=0;i<a.length;i++){ 7 a[i] = 100; 8 } 9 a[3] = 88; //a[3] is false 10 } 11 public static void main(String[] args) { 12 for(int i=0;i<a.length;i++){ 13 System.out.print(" "+"a["+i+"] ="+a[i]); 14 } 15 System.out.println("\n"); 16 int number; 17 number = falseCoin(a,0,a.length-1); //a0~a9 18 System.out.println("找到了! 假币的数组下标是:"+number); 19 } 20 /** 21 * @param int []a,int beginIndex,int endIndex 22 * @param beginIndex: 起始点数组下标 23 * @param endIndex: 终止点数组下标 24 * @return 返回编号 25 */ 26 public static int falseCoin(int []a,int beginIndex,int endIndex){ 27 if(beginIndex + 1 == endIndex){ //如果只剩下最好两枚硬币 28 if(a[beginIndex] > a[endIndex]){ 29 return endIndex; 30 } 31 else{ 32 return beginIndex; 33 } 34 } 35 if((endIndex - beginIndex +1)%2 == 0 ){ //区间个数为偶数 36 int sum1 = 0; 37 int sum2 = 0; 38 for(int i = beginIndex ; i < (beginIndex+endIndex)/2+1; i++){ 39 sum1 = sum1+a[i]; //前半段 40 } 41 for(int j = (beginIndex +endIndex)/2+1; j < endIndex+1; j++){ 42 sum2 = sum2 + a[j]; // 后半段 43 } 44 if(sum1 < sum2){ //假币在左侧 45 System.out.print("假币藏在这其中之一 : "); 46 for(int i = beginIndex ; i < (beginIndex+endIndex)/2+1; i++){ 47 System.out.print(i+","); 48 } 49 System.out.println("\n"); 50 return falseCoin(a,beginIndex,(beginIndex+endIndex)/2); //开始数~中间数向下取整 51 } 52 if(sum1 > sum2){ //假币在右侧s 53 System.out.print("假币藏在这其中之一 : "); 54 for(int j = (beginIndex +endIndex)/2+1; j < endIndex+1; j++){ 55 System.out.print(j+","); 56 } 57 System.out.println("\n"); 58 return falseCoin(a,(beginIndex+endIndex)/2+1,endIndex); //中间数向下取整+1~尾数 59 } 60 } 61 if((endIndex - beginIndex +1)%2 == 1){ //区间个数为奇数(01 | 2 | 34) i=0 62 int sum3 = 0; 63 int sum4 = 0; 64 for(int i=beginIndex;i<(beginIndex+endIndex)/2;i++){ //左半段 65 sum3 = sum3+a[i]; 66 } 67 for(int j=(beginIndex+endIndex)/2+1; j< endIndex+1; j++){ 68 sum4 = sum4+a[j]; 69 } 70 if(sum3<sum4){ 71 System.out.print("假币藏在这其中之一 : "); 72 for(int i=beginIndex;i<(beginIndex+endIndex)/2;i++){ //左半段 73 System.out.println(i+","); 74 } 75 System.out.println("\n"); 76 return falseCoin(a,beginIndex,(beginIndex+endIndex)/2-1); 77 } 78 if(sum3>sum4){ 79 System.out.print("假币藏在这其中之一 : "); 80 for(int j=(beginIndex+endIndex)/2+1; j< endIndex+1; j++){ 81 System.out.print(j+","); 82 } 83 System.out.println("\n"); 84 return falseCoin(a,(beginIndex+endIndex)/2+1,endIndex); 85 } 86 if(sum3 == sum4){ 87 return (beginIndex+endIndex)/2; //中间者是假币,函数立即返回 88 } 89 } 90 return -1; //不存在假币 91 } 92 }
运行结果:
a[0] =100 a[1] =100 a[2] =100 a[3] =88 a[4] =100 a[5] =100 a[6] =100 a[7] =100 a[8] =100 a[9] =100
假币藏在这其中之一 : 0,1,2,3,4,
假币藏在这其中之一 : 3,4,
找到了! 假币的数组下标是:3