1. 程式人生 > >回溯演算法例題分析

回溯演算法例題分析

什麼是回溯演算法

回溯演算法的概念:回溯演算法是一種類似列舉但優於列舉的演算法,它可以通過“剪枝”的方式去掉那些不可能的遞迴情況,從而得到實現解題目標的所有可行方案。回溯演算法一般通過遞迴來實現,每個次遞迴前都要判斷當前這種方案是不是有繼續遞迴的必要,如果可以,就繼續往深入遞迴,最終實現目標,如果在遞迴中發現不符合遞迴條件了,那麼就“減掉”這個分支,程式回溯到之前的另一種可能性重新開始,繼續往下遞迴,能遞迴到最後一步的方案一般稱為可行解,我們可以統計可行解的數目,從而最終得到方案總數。
回溯演算法的優點:回溯演算法可以討論所有可能的方案,不遺漏地進行判斷,同時它也會放棄那些不可能的遞迴方案,節省程式的執行時間。
回溯演算法的解題思路

:首先分析題目,如果最終目標由一系列步驟組成,並且讓你求最終目標的方案數目,那麼可以嘗試使用回溯演算法。程式碼一般分為兩部分,遞迴部分和下一步判斷部分,遞迴部分以迴圈的形式遍歷所有可能的情況,判斷部分的布林值反饋結果決定是否下一步遞迴或者“剪枝”,即寫判斷條件。最終在遞迴達到一定深度的時候判斷方案可行,進行計數累加或儲存方案。

回溯演算法的例題1:N皇后問題

題目:大家常見的是八皇后問題,是N皇后問題的一種特殊情況。N皇后問題是指,在一個N*N的棋盤上,放置N個皇后,他們彼此不能處在相同行、列、對角線,求有多少種擺放方案。
分析:我們可以對N個皇后逐一放置,首先我們可以確定一行擺放一個皇后,每個皇后有N個可以擺放的位置,那麼我們先假設第一個皇后放在第一行第一個位置,再判斷第二個皇后的擺放,如果放在第二行第一個位置在同一列不符合條件,放在第二個位置是對角線也不符合條件,第三個位置就可以,所以放下這個皇后,接下來看第三個皇后的擺放,以此類推,每次判斷不可擺放時,即剪去了這個分支。如果某次遞迴行數大於N,即棋盤擺滿,方案可行,計數加一。
演算法實現

	int[] x ; //先申明一個數組,等待皇后數量輸入
	int sum =0; //滿足條件的情況的累計
	int num ; // 棋盤的寬高
	/**
	 * 回溯演算法1:皇后問題入口方法
	 */
	public void bt1()
	{
		num = 8;
		x= new int[num+1];
		BT1(1); //開始第一次遞迴
		System.out.println(sum); 
	}	
	/**
	 * 回溯演算法1:遞迴方法
	 * @param num
	 * @return
	 */
	public void BT1(int t) //擺第t行
	{	
		if(t > num)  //棋盤擺完,計數加一
			sum++;
        else
        {
			for(int i=1;i<=num;i++)  //從第一個位置開始試探直到最後一個
			{
				x[t]=i;  //放在第i個位置
				if(place(t)==true) //可以放置就開始放下一個
				{
					BT1(t+1);//下一行
				}				
			}
		}
	}
	/**
	 * 回溯演算法1:判斷位置是否可放	
	 */
	public boolean place(int t)
	{
	       for(int i=1;i<t;i++)
	       {
	    	   if(x[i]==x[t]||Math.abs(i-t)==Math.abs(x[i]-x[t]))//判斷對角線及行列
	    	   {
	    		   return false;
	    	   }
	       }		
			return true;
	}

回溯演算法2:圖著色問題

問題:給定一個拼圖,顏色剛開始都是空白的,我們有四種顏色,要求為圖形上色且相鄰色塊顏色不能相同。
分析:類似N皇后問題,遞迴是對於上色順序進行遞迴,試探所有上色可能性,判斷是判斷是否和周圍顏色相同。相鄰關係我們可以用一個二維陣列來儲存(0表示不相鄰,1表示相鄰)。
演算法實現

	int[][] p =     //圖的相鄰關係矩陣,相鄰為1,矩陣長寬和分割槽數量有關
		{
	                {0,1,0,0,0,0,1},    //圖1和1~7的相鄰關係
			{1,0,1,1,1,1,1},
			{0,1,0,1,0,0,0},
			{0,1,1,0,1,0,0},
			{0,1,0,1,0,1,1},
			{0,1,0,0,1,0,1},
			{1,1,0,0,1,1,0}		//圖7和1~7的相鄰關係
		};
	int sum2 = 0; //可行的情況
	int[] y = new int[8]; ;  //上色情況 1和2和3和4表示不同顏色
	int num2=7;
	/**
	 * 回溯演算法2:圖著色問題
	 */
	public void bt2()
	{
		BT2(1);
		System.out.println(sum2);
	}
	/**
	 * 回溯演算法2:遞迴方法
	 * @param t
	 */
	public void BT2(int t)
	{
		if(t>num2)
		{
			sum2++;
		}
		else
		{
			for(int i=1;i<=4;i++) //四種顏色進行嘗試
			{
				y[t]=i;				
				if(place2(t)==true)
				{
					BT2(t+1);
				}
			}
		}		
	}
	/**
	 * 回溯演算法2:判斷
	 * @param t
	 * @return
	 */
	public boolean place2(int t)
	{
		for(int j=1;j<t;j++) //邏輯上的第1個開始
		{
			if(p[t-1][j-1]==1&&y[t]==y[j])
			{
				return false;
			}
		}
		return true;
	}

回溯演算法3:火柴棍擺正方形

題目:給定若干不同長度的火柴棍,要求判斷這些火柴棍能不能圍成正方形。
分析:如果總長不能被4整除,那麼就不能擺成正方形,如果可以被四整除,我們再開始使用回溯演算法遞迴判斷。我們選擇一個數組,表示四個桶,桶的深度是總長的四分之一,我們選擇火柴棍放入桶裡,如果長度超出則表示這種擺放方案不可選,取出並進行下一個桶的試探,當四個桶中火柴棍總長度相同並且火柴棍擺完,那麼這就是一種可行的解。程式遞迴的部分遞迴每個火柴棍的擺放,每個火柴棍有四種擺放方案。
演算法實現

	int[] z ;//定義陣列用來表示放第幾個火柴
	int[] w = new int[5]; //定義每個桶的火柴數量
	int add=0; //火柴總數
	int t=1; //當前放的火柴
	int flag=0;//可否擺成正方形
	/**
	 * 回溯演算法3:火柴棍擺正方形
	 */
	public void bt3()
	{
		int[] c={1,1,1,1,1,1,2};  //各火柴的長度
		z=new int[c.length+1];  //1:表示的一個桶,2:表示第二個桶,3:表示第三個桶 4:表示第四個桶
		add=0;
		for(int i=0;i<c.length;i++)
		{
			add+=c[i];
		}
		System.out.println("當前總長度:"+add);
		if(add%4!=0)
			System.out.println("不可以組成正方形");
		else
		{
	        BT(t,c);	
			if(flag!=1)
				System.out.println("不可以組成正方形");
			else
				System.out.println("可以組成正方形");
		}		
	}	
	/**
	 * 回溯演算法3:遞迴方法
	 * @param t
	 */
	public void BT(int t,int[] c)
	{
		for(int i=1;i<=4;i++)
		{
			if(t==8)
			{
				flag=1;
				break;
			}
			z[t]=i;
			w[i]+=i; 
			if(place3(t)==true) //true則繼續放下一個火柴
			{
				BT(t+1,c); //遞迴放下一個火柴
			}
	        w[i]-=i;//不可以放就把重量減掉
		}
	}	
	/**
	 * 回溯演算法3:判斷可不可以放
	 * @param t
	 */
	public boolean place3(int t)
	{
		for(int i=1;i<5;i++)
		{		
			if(w[i]>add/4)
				return false;
		}		
		return true;
	}

回溯演算法4:求子集

題目:給定一個數組,求它的所有不重複子集。
分析:回溯演算法試探所有擺放的方法,每個數字有放入和不放入兩種方案,遞迴擺放數字(在陣列中儲存),擺完三個就可以去和HashMap中的陣列進行判斷,如果已經存在就不放入,不存在就新增進HashMap。執行完所有情況,HashMap中的元素就是所有的子集。
演算法實現

	ArrayList<int[]> array = new ArrayList<>();
	int[] c ; //用來表示存放的是第幾個數
	int[] temp ; //臨時存放資料
	/**
	 * 回溯演算法:4:求子集
	 */
	public void bt4()
	{
		int[] a = {1,2,3};
		c=new int[a.length+1];
		temp=new int[a.length];
		DT4(1,a);	
		for(int i=0;i<array.size();i++)
		System.out.println(Arrays.toString(array.get(i)));
	}	
	/**
	 * 回溯演算法4:遞迴方法
	 */
	public void DT4(int t,int[] a)
	{
		if(t==a.length+1)
		{
			place4(a);
		}
			for(int i=0;i<=1;i++)  //0:不放   1:放
			{
				if(t==a.length+1)
					break;
				c[t]=i;									
				DT4(t+1,a);				
			}	
	}	
	/**
	 * 回溯演算法4:判斷
	 * @return
	 */
	public void place4(int[] a)
	{
		int count=0;
		for(int j=1;j<a.length+1;j++)    
		{
			if(c[j]==1)  //表示選擇了這個數
			{
				temp[count]=a[j-1];
				count++;     //統計要放入的數的數量
			}
		}
		int[] r = new int[count];  //定義一個長度和所選數字數量一致的陣列
		for(int j=0;j<count;j++) //存值
			r[j]=temp[j];
        if(!array.contains(r)&&r.length!=0) //判斷是否存在
        	array.add(r);  //新增
	}