问题的描述:一摞大小无序的饼,允许单个或者多个一起直接的颠倒,求最优化的翻饼过程。
刚看到题目的时候,感觉比较的简单,就想一个数组,加上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较大的情况下,这个是不大现实的,可以利用比较简单的算法,圈定一定的空间来进行处理。这个算作是一个普通的想法吧,另外这中算法是分支限界法(即遍历+剪枝=分支限界),以后交流的时候,可能会用到。
关于这个,这个问题的动态规划或者贪心算法,会在下一个慢慢的研究。