1. 回溯法的用回溯法解題的一般步驟
(1)針對所給問題,定義問題的解空間;
(2)確定易於搜索的解空間結構;
(3)以深度優先方式搜索解空間,並在搜索過程中用剪枝函數避免無效搜索。
回溯法C語言舉例
八皇後問題是能用回溯法解決的一個經典問題。
八皇後問題是一個古老而著名的問題。該問題是十九世紀著名的數學家高斯1850年提出:在8X8格的國際象棋上擺放八個皇後,使其不能互相攻擊,即任意兩個皇後都不能處於同一行、同一列或同一對角線上,問有多少種擺法。引入一個整型一維數組col[]來存放最終結果,col[i]就表示在棋盤第i列、col[i]行有一個皇後,為了使程序再找完了全部解後回到最初位置,設定col[0]的初值為0,即當回溯到第0列時,說明以求得全部解,結束程序運行。為了方便演算法的實現,引入三個整型數組來表示當前列在三個方向上的狀態 :
a[] a[i]=0表示第i行上還沒有皇後;
b[] b[i]=0表示第i列反斜線/上沒有皇後;
c[] c[i]=0表示第i列正斜線上沒有皇後。
棋盤中同一反斜線/上的方格的行號與列號之和相同;同一正斜線上的方格的行號與列號之差均相同,這就是判斷斜線的依據。
初始時,所有行和斜線上都沒有皇後,從第1列的第1行配置第一個皇後開始,在第m列,col[m]行放置了一個合理的皇後,准備考察第m+1列時,在數組a[],b[]和c[]中為第m列,col[m]行的位置設定有皇後的標志;當從第m列回溯到m-1列時,並准備調整第m-1列的皇後配置時,清除在數組a[],b[]和c[]對應位置的值都為1來確定。 #include<stdio.h>
#include<stdlib.h>
#define Queens 8
int a[Queens+1]; //八皇後問題的皇後所在每一行位置,從1開始算
int main()
{
int i,k,flag,not_finish=1,count=0;
i=1;//初始
a[1]=1;
printf(the possible configuration of 8 queesns are:
);
while(not_finish) //not_finsh=1:處理未結束
{
while(not_finish && i<Queens+1) //處理未結束
{
for(flag=1,k=1;flag && k<i;k++)//判斷是否有多個皇後在同一行
if(a[k]==a[i])
flag=0;
for(k=1;flag && k<i;k++) //判斷是否有多個皇後在對角線
if((a[i]==a[k]-(k-i))||(a[i]==a[k]+(k-i)))
flag=0;
if(!flag) //若存在矛盾 重設第i個元素
{
if(a[i]==a[i-1]) //若a[i]的值已經已經一圈追上a[i-1]的值
{
i--; //退回一步 重新試探處理前一個元素
if(i>1 && a[i]==Queens)
a[i]=1; // 當a[i]為 Queens時 將a[i]的值重置
else
if(i==1 && a[i]==Queens)//當第一未位的值達到Queens時結束
not_finish=0;
else
a[i]++;
}
else
if(a[i]==Queens)
a[i]=1;
else
a[i]++;
}
else
if(++i<=Queens) //若前一個元素的值為Queens
if(a[i-1]==Queens)
a[i]=1;
else //否則元素為前一個元素的下一個值
a[i]=a[i-1]+1;
}
if (not_finish)
{
++count;
printf((count-1)%3?[%2d]::
[%2d]:,count);
for(k=1;k<=Queens;k++) //輸出結果
printf(%d,a[k]);
if(a[Queens-1]<Queens)
a[Queens-1]++;
else
a[Queens-1]=1;
i=Queens-1;
}
}
system(pause);
} var
n,k,t,i:longint;
x:array[1..100] of integer;
function pa(k:integer):boolean;
begin
pa:=true;
for i:=1 to k-1 do
if (x[i]=x[k]) or (abs(x[i]-x[k])=abs(i-k)) then pa:=false;
end;
procere try(k:integer);
var
i:integer;
begin
if k>n then
begin
t:=t+1;
exit;
end;
for i:=1 to n do
begin
x[k]:=i;
if pa(k) then try(k+1);
end;
end;
begin
read(n);
t:=0;
try(1);
write(t);
end. #include
#include
#define m 5
#define n 6
int sf=0;
int mase[m][n]={{0,0,0,1,0,0},{0,1,0,0,0,0},{0,1,1,1,1,0},{0,0,0,0,0,1},{1,0,1,1,0,0}};
void search(int x,int y)
{
if((x==m-1)&&(y==n-1))
sf=1;
else
{
mase[x][y]=1;
if((sf!=1)&&(y!=n-1)&&mase[x][y+1]==0)
search(x,y+1);
if((sf!=1)&&(x!=m-1)&&mase[x+1][y]==0)
search(x+1,y);
if((sf!=1)&&(y!=0)&&mase[x][y-1]==0)
search(x,y-1);
if((sf!=1)&&(x!=0)&&mase[x-1][y]==0)
search(x-1,y);
}
mase[x][y]=0;
if(sf==1)
mase[x][y]=5;//通過路徑用數字的表示
}
int main()
{
int i=0,j=0;
//clrscr();
search(0,0);
for(i=0;i<m;i++) p=></m;i++)>
{
for(j=0;j<n;j++) p=></n;j++)>
printf(%d,mase[i][j]);
printf(
);
}
system(pause);
return 0;
}
回溯法解決迷宮問題PASCAL語言
program migong;
var
n,k,j,x,y:integer;
a:array[0..10000,0..10000] of integer;
b:array[0..1000000,0..2] of integer;
procere search(x,y,i:integer);
begin
a[x,y]:=1;
if (x=n) and (y=n) then
begin
for j:=1 to i-1 do
writeln(j,':(',b[j,1],',',b[j,2],')');
writeln(i,':(',x,',',y,')');
halt;
end;
if a[x-1,y]=0 then begin b[i,1]:=x;b[i,2]:=y;search(x-1,y,i+1);end;
if a[x+1,y]=0 then begin b[i,1]:=x;b[i,2]:=y;search(x+1,y,i+1);end;
if a[x,y-1]=0 then begin b[i,1]:=x;b[i,2]:=y;search(x,y-1,i+1);end;
if a[x,y+1]=0 then begin b[i,1]:=x;b[i,2]:=y;search(x,y+1,i+1);end;
a[x,y]:=0;
end;
begin
read(n);
for k:=1 to n do
for j:=1 to n do
read(a[k,j]);
for k:=0 to n+1 do
begin
a[k,0]:=-1;
a[k,n+1]:=-1;
a[n+1,k]:=-1;
a[0,k]:=-1;
end;
x:=1;y:=1;
if a[x+1,y]=0 then begin a[x,y]:=1;b[1,1]:=x;b[1,2]:=y;search(x+1,y,1);a[x,y]:=0;end;
if a[x,y+1]=0 then begin a[x,y]:=1;b[1,1]:=x;b[1,2]:=y;search(x,y+1,1);a[x,y]:=0;end;
end.
2. 常見演算法思想6:回溯法
回溯法也叫試探法,試探的處事方式比較委婉,它先暫時放棄關於問題規模大小的限制,並將問題的候選解按某種順序逐一進行枚舉和檢驗。當發現當前候選解不可能是正確的解時,就選擇下一個候選解。如果當前候選解除了不滿足問題規模要求外能夠滿足所有其他要求時,則繼續擴大當前候選解的規模,並繼續試探。如果當前候選解滿足包括問題規模在內的所有要求時,該候選解就是問題的一個解。在試探演算法中,放棄當前候選解,並繼續尋找下一個候選解的過程稱為回溯。擴大當前候選解的規模,以繼續試探的過程稱為向前試探。
(1)針對所給問題,定義問題的解空間。
(2)確定易於搜索的解空間結構。
(3)以深度優先方式搜索解空間,並在搜索過程中用剪枝函數避免無效搜索。
回溯法為了求得問題的正確解,會先委婉地試探某一種可能的情況。在進行試探的過程中,一旦發現原來選擇的假設情況是不正確的,馬上會自覺地退回一步重新選擇,然後繼續向前試探,如此這般反復進行,直至得到解或證明無解時才死心。
下面是回溯的3個要素。
(1)解空間:表示要解決問題的范圍,不知道範圍的搜索是不可能找到結果的。
(2)約束條件:包括隱性的和顯性的,題目中的要求以及題目描述隱含的約束條件,是搜索有解的保證。
(3)狀態樹:是構造深搜過程的依據,整個搜索以此樹展開。
下面是影響演算法效率的因素:
回溯法搜索解空間時,通常採用兩種策略避免無效搜索,提高回溯的搜索效率:
為縮小規模,我們用顯示的國際象棋8*8的八皇後來分析。按照國際象棋的規則,皇後的攻擊方式是橫,豎和斜向。
皇後可以攻擊到同一列所有其它棋子,因此可推導出每1列只能存在1個皇後,即每個皇後分別占據一列。棋盤一共8列,剛好放置8個皇後。
為了擺放出滿足條件的8個皇後的布局,可以按如下方式逐步操作:
把規模放大到N行N列也一樣,下面用回溯法解決N皇後問題:
執行:
3. 9.2 回溯演算法的例子
在4 * 4的方格棋盤上放置4個皇後棋子,使得沒有兩個皇後在同一行、同一列,也不在同一條45度的斜線上, 問有多少種布局?
回溯演算法的解一般是向量,而這個題也不例外,設4維向量的<x1,x2,x3,x4>,Xi中i表示第幾個皇後,Xi表示在棋盤第i行的位置,比如其中一個解是<2,4,1,3>,如下圖
1.四皇後問題中,葉節點就是一個解。
2.四皇後每一個節點的子樹代表著下一個皇後可以放的列數,因為都是n,所以子樹都是n叉樹,故四皇後是一顆 n叉樹
3.四皇後的解至少有兩個,因為棋盤可以沿著中心線翻折
有n種物品,每種物品只有1個。第i種物品價值為vi,重量為wi,i=1,2,3...n. 問如何選擇放入背包的物品,使得總重量不超過B,而價值達到最大?
同樣,此問題的解可用一個向量來表示,該向量就代表了所有的物品,如果對應物品為1,則表示裝入背包,反之,沒有被裝入。
因此,回溯的每層可以表示為對應的物品,分支左右可以表示取或者不取(向量中表示為1或0)
總而言之,每一個節點也就是物品只有0和1兩種狀態,因此該樹一棵二叉樹,或者為 子集樹
1.選擇第一個物品,目前總重量為8,總價值為12。
2.再選擇第二個物品,總重量為14 > 13,觸發回溯。
3.不選擇第二個物品,選擇第三個商品,總重量是12,總價值為21。
4.再選擇第四個物品,總重量為15 > 13,觸發回溯。
5.不選擇第四個物品,總重量為12,總價值為21,與目前最優解價值進行比較,如果最優解價值大於21則替換目前的最優解向量和最優解價值。
1.背包問題只有在葉節點才能生成一個滿足條件的解,而之後將該解和最優解比較。
2.背包問題必須遍歷完所有的分支,才能夠獲得最終的解。
3.背包問題是一顆子集樹。
有n個城市,已知任兩個城市之間的距離, 求一條每個城市恰好經過一次的迴路,使得總長度最小 。
貨郎問題中主要的一點就是每一個點(除了第一個點)其他點必須經過且只能經過1次,這就很像數學中的排列。
因此,我們採用一個向量來表示貨郎問題的城市排列
1.貨郎問題是一顆分支不斷減少的排列數(和數學的排列類似)
2.貨郎問題也得遍歷完所有的情況,比較後得出最優解。
1.解都是用向量表示
2.搜索空間都是樹
3.搜索策略多種,有深度優先、寬度優先和跳躍式遍歷搜索樹。
4. 五大基本演算法——回溯法
回溯法是一種選優搜索法(試探法)。
基本思想:將問題P的狀態空間E表示成一棵高為n的帶全有序樹T,把求解問題簡化為搜索樹T。搜索過程採用 深度優先搜索 。搜索到某一結點時判斷該結點是否包含原問題的解,如果包含則繼續往下搜索,如果不包含則向祖先回溯。
通俗來說,就是利用一個樹結構來表示解空間,然後從樹的根開始深度優先遍歷該樹,到不滿足要求的葉子結點時向上回溯繼續遍歷。
幾個結點:
擴展結點:一個正在產生子結點的結點稱為擴展結點
活結點:一個自身已生成但未全部生成子結點的結點
死結點:一個所有子結點已全部生成的結點
1、分析問題,定義問題解空間。
2、根據解空間,確定解空間結構,得 搜索樹 。
3、從根節點開始深度優先搜索解空間(利用 剪枝 避免無效搜索)。
4、遞歸搜索,直到找到所要求的的解。
1、子集樹
當問題是:從n個元素的集合S中找出滿足某種性質的子集時,用子集樹。
子集樹必然是一個二叉樹。常見問題:0/1背包問題、裝載問題。
遍歷子集樹時間復雜度:O(2^n)
2、排列樹
當問題是:確定n個元素滿足某種排列時,用排列數。常見問題:TSP旅行商問題,N皇後問題。
遍歷排列樹時間復雜度:O(n!)
通俗地講,結合Java集合的概念,選擇哪種樹其實就是看最後所得結果是放入一個List(有序)里,還是放入一個Set(無序)里。
剪枝函數能極大提高搜索效率,遍歷解空間樹時,對於不滿足條件的分支進行剪枝,因為這些分支一定不會在最後所求解中。
常見剪枝函數:
約束函數(對解加入約束條件)、限界函數(對解進行上界或下界的限定)
滿足約束函數的解才是可行解。
1、0/1背包問題
2、TSP旅行商問題
3、最優裝載問題
4、N-皇後問題
具體問題可網路詳細內容。
5. 什麼是回溯演算法
回溯演算法也叫試探法,它是一種系統地搜索問題的解的方法。回溯演算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。用回溯演算法解決問題的一般步驟為: 1、定義一個解空間,它包含問題的解。 2、利用適於搜索的方法組織解空間。 3、利用深度優先法搜索解空間。 4、利用限界函數避免移動到不可能產生解的子空間。 問題的解空間通常是在搜索問題的解的過程中動態產生的,這是回溯演算法的一個重要特性。 1.跳棋問題: 33個方格頂點擺放著32枚棋子,僅中央的頂點空著未擺放棋子。下棋的規則是任一棋子可以沿水平或成垂直方向跳過與其相鄰的棋子,進入空著的頂點並吃掉被跳過的棋子。試設計一個演算法找出一種下棋方法,使得最終棋盤上只剩下一個棋子在棋盤中央。 演算法實現提示 利用回溯演算法,每次找到一個可以走的棋子走動,並吃掉。若走到無子可走還是剩餘多顆,則回溯,走下一顆可以走動的棋子。當吃掉31顆時說明只剩一顆,程序結束。 2.中國象棋馬行線問題: 中國象棋半張棋盤如圖1(a)所示。馬自左下角往右上角跳。今規定只許往右跳,不許往左跳。比如 圖4(a)中所示為一種跳行路線,並將所經路線列印出來。列印格式為: 0,0->2,1->3,3->1,4->3,5->2,7->4,8… 演算法分析: 如圖1(b),馬最多有四個方向,若原來的橫坐標為j、縱坐標為i,則四個方向的移動可表示為: 1: (i,j)→(i+2,j+1); (i<3,j<8) 2: (i,j)→(i+1,j+2); (i<4,j<7) 3: (i,j)→(i-1,j+2); (i>0,j<7) 4: (i,j)→(i-2,j+1); (i>1,j<8) 搜索策略: S1:A[1]:=(0,0); S2:從A[1]出發,按移動規則依次選定某個方向,如果達到的是(4,8)則轉向S3,否則繼續搜索下 一個到達的頂點; S3:列印路徑。 演算法設計: procere try(i:integer); {搜索} var j:integer; begin for j:=1 to 4 do {試遍4個方向} if 新坐標滿足條件 then begin 記錄新坐標; if 到達目的地 then print {統計方案,輸出結果} else try(i+1); {試探下一步} 退回到上一個坐標,即回溯; end; end;
6. 跪求演算法大牛解釋一下青蛙跳,也就是綠色和褐色青蛙互換問題的回溯演算法!!!
Frog[191] :初始化游戲中的n只青蛙,以1-n/2代表左邊的青蛙,n/2+1-n代表右邊的青蛙,
Frog[]初始化即為青蛙最初的位置,如果輸入n=7,Frog[]={1,2,3,0,4,5,6}
Done[191] :該數組值只取0或1,其中1代表用於表示此刻,在第i(1-n) 墩上的青蛙已換位成功。反之,取0。
DO[1926] :該數組用於記錄空墩移位的狀況,元素的取值范圍為0-n-1(在計算機中的表示)。根據在第i步,可以根據元素值DO[i-1] 進行回溯操作。
這個演算法中DO[ ]記錄的是石頭的移動步驟,回溯的其實是回溯DO[ ],返回上一步的操作
7. 暴力窮舉和回溯法(八皇後問題)
以前每次遇到演算法問題都是直接暴力求解,一直以為自己用的是暴力窮舉法,現在學了回溯法,發現部分問題其實使用的是回溯法,而不是單純的暴力窮舉。
例如求解一個n皇後問題:
1.使用暴力窮舉,由於沒有兩個皇後能夠放在一列上,激塵櫻那麼解向量一定是數1,2,····,n的一個排列(第一行n種放法,第二行n-1種,以此類推)。時間復雜度O(n!).
為什麼是一維而不是兩維?因為沒有兩個皇後能在同一列,所以只用行標志就可以表示出皇後的位置,簡化了問題
2.回溯法,就等於是一個一個的試,從1到n,時間復雜度O(n^n),每一行n種放法,總共n行。
看起來回溯法要比暴力窮舉差很多,但是實際上回溯法很多時候實際演算法復雜度並沒有暴力窮舉高。比如4皇後問題中,僅需要341個可能節點中的27個節點就可以找到解,但是暴力窮舉實際會慢很多。
換一個思路,比如第一個皇後放在了0位置,暴力窮舉第二個皇後放在1位置,那麼之後的皇後無論怎麼放都是錯誤的,也就是(n-2)!個向量全部都是錯誤的,而回溯法面對這種問題,會在之前就直接拋棄這種兄塌情況,速度會快很多。不要問為什麼暴力窮舉為什麼不學回溯法那樣提前拋棄,因為它是 暴力窮舉 (這還算優化過一次,不然直接O(n^n))。
總而言之,回溯法並不明叢需要得到所有情況,而且運行過程中會提前拋棄不合要求的情況,所以演算法復雜度一般不會到最差的情況。