编程之美读书笔记:翻烙饼的问题

来源:互联网 发布:Java processstartinfo 编辑:程序博客网 时间:2024/05/05 03:10

问题的描述:一摞大小无序的饼,允许单个或者多个一起直接的颠倒,求最优化的翻饼过程。

刚看到题目的时候,感觉比较的简单,就想一个数组,加上for循环,再加上几个条件判断就好了。只是一个程序,丝毫没有算法什么的概念,或者说就是为了解题而解题。看了书上面的分析,有点看不大懂的感觉,细细的看,才觉得这个才是问题沉淀的一个过程,或者说这样才是积累的一个过程。就比如高中老师说的那样:会一道题,算什么,要根据一道题会一片。

首先是,分析的一个过程,一般来说对于问题的,穷举都能有一定的帮助,对于问题的理解和发现其中的规律。现在是我们想到是首先找到最大的数,把它翻转到最下面。然后顺着这个思路进行分析。


粗略的搜索最优的解决的方案,是首把所有的可能的翻转方案过一篇,记录者最小的,利用的是递归的实现,递归的结束条件,一个是翻转的次是必须小于分析的最大值,一个是一个完成了。


java代码实现:

public class CakeTurn {public int m_nCakeCnt ;//烙饼的个数public int[] m_cakeArray ;//烙饼的半径的数组public int m_nMaxSwap;//最大的反转的次数public int[] m_swapArray;//交换的结果的数组public int[] m_reverseCakeArray;//当前翻转烙饼信息数组public int[] m_reverseCakeArraySwap;//当前翻转烙饼交换结果数组int m_nSearch;//当前搜索次数信息public void run(int[] cakeArray,int cakeNumber){//初始化数组init(cakeArray,cakeNumber);m_nSearch = 0;search(0);}public void output(){for (int i = 0; i < m_nMaxSwap; i++) {System.out.print("  "+m_swapArray[i]);}System.out.println(" searchTimes: " + m_nSearch); System.out.println(" Total swap times : " + m_nMaxSwap);}//搜索private void search(int step) {//首先是搜索的步骤,每一步的搜索都记录,用于后面的计算 int i, nEstimate;        m_nSearch++;                //剪枝条件,估计这个剪枝的判断,是相邻的数字不计入翻转的次数的,        nEstimate = lowerBound(m_reverseCakeArray);        if( step + nEstimate > m_nMaxSwap || step >= m_nMaxSwap)            return;        //是否翻转完成        if( isSorted( m_reverseCakeArray)){            if( step < m_nMaxSwap){//取翻转的次数                m_nMaxSwap = step;                for(i = 0; i < m_nMaxSwap; i++)                    m_swapArray[i] = m_reverseCakeArraySwap[i];//记录翻转的结果            }            return;        }                for(i = 1; i < m_nCakeCnt; i++){            revert(0,i);            System.out.println(Arrays.toString(m_reverseCakeArray));//打印翻转过程中的,过程的变化            m_reverseCakeArraySwap[step] = i;            search(step+1);            revert(0,i);//并不是一开始找到了能够翻转好的方法,就完了,而是要确定最小的翻转的次数。        }            }private void revert(int begine, int end) {int i = 0,j=0,t=0;//翻转烙饼的信息for (i = begine,j= end ; i < j; i++,j--) {t = m_reverseCakeArray[i];m_reverseCakeArray[i] = m_reverseCakeArray[j];m_reverseCakeArray[j] = t;}}//判断是否已经排好了续private boolean isSorted(int[] reverseCakeArray) {for (int i = 1; i < reverseCakeArray.length; i++) {if(reverseCakeArray[i-1] > reverseCakeArray[i]){return false;}}return true;}//估计这次搜索所需要的最小的交换的次数private int lowerBound(int[] cakeArray) {int t,ret = 0;//根据当前数组的排列顺序来判断最少需要交换多少次for(int i=1;i<cakeArray.length;i++){t = cakeArray[i]- cakeArray[i-1];if(t==-1||t==1){}else{ret++;}}return ret;}//初始化烙饼的信息:半径的信息组和烙饼的数量public void init(int[] cakeArray2, int cakeNumber2) {m_nCakeCnt = cakeNumber2;m_cakeArray = new int[m_nCakeCnt];for(int i = 0 ; i< cakeArray2.length;i++){m_cakeArray[i] = cakeArray2[i];}//最大的次数的获得m_nMaxSwap = upperBound(m_nCakeCnt);//设置初始化交换结果的数组m_swapArray = new int[m_nMaxSwap];//初始化中间交换结果的信息m_reverseCakeArray = new int[m_nCakeCnt];for (int i = 0; i < m_nCakeCnt; i++) {m_reverseCakeArray[i] = m_cakeArray[i];}m_reverseCakeArraySwap = new int[m_nMaxSwap];}//寻找当前翻转的最大值private int upperBound(int cakeNumber2) {return cakeNumber2*2;}public static void main(String[] args){CakeTurn problem = new CakeTurn();//int[] cakeArray = {3,2,1,6,5,4,9,8,7,0};//int[] cakeArray = {3,2,1,6,5,4};int[] cakeArray = {3,2,1};        problem.run(cakeArray,cakeArray.length);        problem.output();}}
看到这个逻辑的实现,还是感觉比较的舒服的。最重要的是理解,这段代码:

 for(i = 1; i < m_nCakeCnt; i++){            //任何的时候都是从第一个到第几个开始翻转,所以首先得是从1到length-1来遍历,表现在程序中,就是i            revert(0,i);            System.out.println(Arrays.toString(m_reverseCakeArray));//打印翻转过程中的,过程的变化            //记录每一步,我从一到第几块饼(i)的翻转,也就是翻转的过程的记录            m_reverseCakeArraySwap[step] = i;            //既然第一步是从一开始便利,那么相同的第二步的逻辑还是和第一步一样的,从上面数几块饼进行翻转。            //没有退出条件的话,会一直的循环下去。所以我们在search中有自己的推出的条件,剪枝和是否完成            search(step+1);            revert(0,i);//并不是一开始找到了能够翻转好的方法,就完了,而是要确定最小的翻转的次数。        }

最简单的{3,2,1} 结果打印出来是,打印的是翻转过程中烙饼信息的变化:

[2, 3, 1]
[3, 2, 1]
[2, 3, 1]
[3, 2, 1]
[2, 3, 1]
[3, 2, 1]
[1, 3, 2]
[1, 2, 3]
[1, 3, 2]
[3, 1, 2]
[2, 3, 1]
[1, 2, 3]
[1, 3, 2]
[3, 1, 2]
[2, 3, 1]
[1, 2, 3]
  2 searchTimes: 17
 Total swap times : 1

首先是,根据结果集能够,更好的理解程序,在者就是说,可以根据结果集,优化程序。书中提到了三种优化程序的方法,上限下压,下限上提,驱除重复的状态。

上限,从cakeNumber*2,变化为(cakeNumber-1)*2,

 4  8  6  8  4  9 searchTimes: 164872
 Total swap times : 6

变为:

 4  8  6  8  4  9 searchTimes: 141787
 Total swap times : 6

下限书上面有具体的说明。

最大下界[15n/14],最小的上界是:[(5n+5)/3]

有时间研究一下,比尔盖茨的论文:http://www.eecs.berkeley.edu/~christos/papers/GP79.pdf

关于如何发现状态重复的问题,如果使用 m_reverseCakeArray,可能是n!对于n较大的情况下,这个是不大现实的,可以利用比较简单的算法,圈定一定的空间来进行处理。这个算作是一个普通的想法吧,另外这中算法是分支限界法(即遍历+剪枝=分支限界),以后交流的时候,可能会用到

关于这个,这个问题的动态规划或者贪心算法,会在下一个慢慢的研究。