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

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

刚看到题目的时候,感觉比较的简单,就想一个数组,加上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较大的情况下,这个是不大现实的,可以利用比较简单的算法,圈定一定的空间来进行处理。这个算作是一个普通的想法吧,另外这中算法是分支限界法(即遍历+剪枝=分支限界),以后交流的时候,可能会用到

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




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值