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