五大常用算法之回溯法
看了五大常用算法之一這篇博文,感覺(jué)理解了很多,可是純粹都是理論,缺少一些示例,所以準(zhǔn)備綜合一篇博文,以幫助自己記憶,原文:
http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741376.html
1、概念
????? 回溯算法實(shí)際上一個(gè)類(lèi)似枚舉的搜索嘗試過(guò)程,主要是在搜索嘗試過(guò)程中尋找問(wèn)題的解,當(dāng)發(fā)現(xiàn)已不滿(mǎn)足求解條件時(shí),就“回溯”返回,嘗試別的路徑。
?? 回溯法是一種選優(yōu)搜索法,按選優(yōu)條件向前搜索,以達(dá)到目標(biāo)。但當(dāng)探索到某一步時(shí),發(fā)現(xiàn)原先選擇并不優(yōu)或達(dá)不到目標(biāo),就退回一步重新選擇,這種走不通就退回再走的技術(shù)為回溯法,而滿(mǎn)足回溯條件的某個(gè)狀態(tài)的點(diǎn)稱(chēng)為“回溯點(diǎn)”。
???? 許多復(fù)雜的,規(guī)模較大的問(wèn)題都可以使用回溯法,有“通用解題方法”的美稱(chēng)。
2、基本思想
???在包含問(wèn)題的所有解的解空間樹(shù)中,按照深度優(yōu)先搜索的策略,從根結(jié)點(diǎn)出發(fā)深度探索解空間樹(shù)。當(dāng)探索到某一結(jié)點(diǎn)時(shí),要先判斷該結(jié)點(diǎn)是否包含問(wèn)題的解,如果包含,就從該結(jié)點(diǎn)出發(fā)繼續(xù)探索下去,如果該結(jié)點(diǎn)不包含問(wèn)題的解,則逐層向其祖先結(jié)點(diǎn)回溯。(其實(shí)回溯法就是對(duì)隱式圖的深度優(yōu)先搜索算法)。
?????? 若用回溯法求問(wèn)題的所有解時(shí),要回溯到根,且根結(jié)點(diǎn)的所有可行的子樹(shù)都要已被搜索遍才結(jié)束。
?????? 而若使用回溯法求任一個(gè)解時(shí),只要搜索到問(wèn)題的一個(gè)解就可以結(jié)束。
3、用回溯法解題的一般步驟:
??? (1)針對(duì)所給問(wèn)題,確定問(wèn)題的解空間:
??????????? 首先應(yīng)明確定義問(wèn)題的解空間,問(wèn)題的解空間應(yīng)至少包含問(wèn)題的一個(gè)(最優(yōu))解。
??? (2)確定結(jié)點(diǎn)的擴(kuò)展搜索規(guī)則
??? (3)以深度優(yōu)先方式搜索解空間,并在搜索過(guò)程中用剪枝函數(shù)避免無(wú)效搜索。
4、算法框架
???? (1)問(wèn)題框架
????? 設(shè)問(wèn)題的解是一個(gè)n維向量(a1,a2,………,an),約束條件是ai(i=1,2,3,…..,n)之間滿(mǎn)足某種條件,記為f(ai)。
???? (2)非遞歸回溯框架
1: int a[n],i; 2: 初始化數(shù)組a[]; 3: i = 1; 4: while (i>0(有路可走) and (未達(dá)到目標(biāo))) // 還未回溯到頭 5: { 6: if(i > n) // 搜索到葉結(jié)點(diǎn) 7: { 8: 搜索到一個(gè)解,輸出; 9: } 10: else // 處理第i個(gè)元素 11: { 12: a[i]第一個(gè)可能的值; 13: while(a[i]在不滿(mǎn)足約束條件且在搜索空間內(nèi)) 14: { 15: a[i]下一個(gè)可能的值; 16: } 17: if(a[i]在搜索空間內(nèi)) 18: { 19: 標(biāo)識(shí)占用的資源; 20: i = i+1; // 擴(kuò)展下一個(gè)結(jié)點(diǎn) 21: } 22: else 23: { 24: 清理所占的狀態(tài)空間; // 回溯 25: i = i –1; 26: } 27: }(3)遞歸的算法框架
???????? 回溯法是對(duì)解空間的深度優(yōu)先搜索,在一般情況下使用遞歸函數(shù)來(lái)實(shí)現(xiàn)回溯法比較簡(jiǎn)單,其中i為搜索的深度,框架如下:
1: int a[n]; 2: try(int i) 3: { 4: if(i>n) 5: 輸出結(jié)果; 6: else 7: { 8: for(j = 下界; j <= 上界; j=j+1) // 枚舉i所有可能的路徑 9: { 10: if(fun(j)) // 滿(mǎn)足限界函數(shù)和約束條件 11: { 12: a[i] = j; 13: ... // 其他操作 14: try(i+1); 15: 回溯前的清理工作(如a[i]置空值等); 16: } 17: } 18: } 19: }
回溯法示例:
(1)深度優(yōu)先算法(DFS)
這是數(shù)據(jù)結(jié)構(gòu)中的基本算法
深度優(yōu)先搜索DFS可描述為:
(1)訪問(wèn)v0頂點(diǎn);
(2)置?visited[v0]=1;
(3)搜索v0未被訪問(wèn)的鄰接點(diǎn)w,若存在鄰接點(diǎn)w,則DFS(w)。
/* 圖的深度優(yōu)先遍歷 出處:一條魚(yú)@博客園 http://www.cnblogs.com/yanlingyin/ 2011-12-26 */ #include <stdlib.h> #include <stdio.h>struct node /* 圖頂點(diǎn)結(jié)構(gòu)定義 */ {int vertex; /* 頂點(diǎn)數(shù)據(jù)信息 */struct node *nextnode; /* 指下一頂點(diǎn)的指標(biāo) */ }; typedef struct node *graph; /* 圖形的結(jié)構(gòu)新型態(tài) */ struct node head[9]; /* 圖形頂點(diǎn)數(shù)組 */ int visited[9]; /* 遍歷標(biāo)記數(shù)組 *//********************根據(jù)已有的信息建立鄰接表********************/ void creategraph(int node[20][2],int num)/*num指的是圖的邊數(shù)*/ {graph newnode; /*指向新節(jié)點(diǎn)的指針定義*/graph ptr;int from; /* 邊的起點(diǎn) */int to; /* 邊的終點(diǎn) */int i;for ( i = 0; i < num; i++ ) /* 讀取邊線(xiàn)信息,插入鄰接表*/{from = node[i][0]; /* 邊線(xiàn)的起點(diǎn) */to = node[i][1]; /* 邊線(xiàn)的終點(diǎn) *//* 建立新頂點(diǎn) */newnode = ( graph ) malloc(sizeof(struct node));newnode->vertex = to; /* 建立頂點(diǎn)內(nèi)容 */newnode->nextnode = NULL; /* 設(shè)定指標(biāo)初值 */ptr = &(head[from]); /* 頂點(diǎn)位置 */while ( ptr->nextnode != NULL ) /* 遍歷至鏈表尾 */ptr = ptr->nextnode; /* 下一個(gè)頂點(diǎn) */ptr->nextnode = newnode; /* 插入節(jié)點(diǎn) */} }/********************** 圖的深度優(yōu)先搜尋法********************/ void dfs(int current) {graph ptr;visited[current] = 1; /* 記錄已遍歷過(guò) */printf("vertex[%d]\n",current); /* 輸出遍歷頂點(diǎn)值 */ptr = head[current].nextnode; /* 頂點(diǎn)位置 */while ( ptr != NULL ) /* 遍歷至鏈表尾 */{if ( visited[ptr->vertex] == 0 ) /* 如過(guò)沒(méi)遍歷過(guò) */dfs(ptr->vertex); /* 遞回遍歷呼叫 */ptr = ptr->nextnode; /* 下一個(gè)頂點(diǎn) */} }/****************************** 主程序******************************/ int main() {graph ptr;int node[20][2] = { {1, 2}, {2, 1}, /* 邊線(xiàn)數(shù)組 */{1, 3}, {3, 1},{1, 4}, {4, 1},{2, 5}, {5, 2},{2, 6}, {6, 2},{3, 7}, {7, 3},{4, 7}, {4, 4},{5, 8}, {8, 5},{6, 7}, {7, 6},{7, 8}, {8, 7} };int i;//clrscr();for ( i = 1; i <= 8; i++ ) /* 頂點(diǎn)數(shù)組初始化 */{head[i].vertex = i; /* 設(shè)定頂點(diǎn)值 */head[i].nextnode = NULL; /* 指針為空 */visited[i] = 0; /* 設(shè)定遍歷初始標(biāo)志 */}creategraph(node,20); /* 建立鄰接表 */printf("Content of the gragh's ADlist is:\n");for ( i = 1; i <= 8; i++ ){printf("vertex%d ->",head[i].vertex); /* 頂點(diǎn)值 */ptr = head[i].nextnode; /* 頂點(diǎn)位置 */while ( ptr != NULL ) /* 遍歷至鏈表尾 */{printf(" %d ",ptr->vertex); /* 印出頂點(diǎn)內(nèi)容 */ptr = ptr->nextnode; /* 下一個(gè)頂點(diǎn) */}printf("\n"); /* 換行 */}printf("\nThe end of the dfs are:\n");dfs(1); /* 打印輸出遍歷過(guò)程 */printf("\n"); /* 換行 */puts(" Press any key to quit...");// getch(); }
代碼來(lái)自http://www.cnblogs.com/yanlingyin/archive/2011/12/26/Depth-firstsearch.html
(2)八皇后問(wèn)題
這是一個(gè)以國(guó)際象棋為背景的問(wèn)題:如何能夠在?8×8?的國(guó)際象棋棋盤(pán)上放置八個(gè)皇后,使得任何一個(gè)皇后都無(wú)法直接吃掉其他的皇后?為了達(dá)到此目的,任兩個(gè)皇后都不能處于同一條橫行、縱行或斜線(xiàn)上。八皇后問(wèn)題可以推廣為更一般的n皇后擺放問(wèn)題:這時(shí)棋盤(pán)的大小變?yōu)?/span>n×n,而皇后個(gè)數(shù)也變成n。當(dāng)且僅當(dāng)?n?= 1?或?n?≥?4?時(shí)問(wèn)題有解。
八皇后問(wèn)題一共有?92?個(gè)互不相同的解。如果將旋轉(zhuǎn)和對(duì)稱(chēng)的解歸為一種的話(huà),則一共有12個(gè)獨(dú)立解
基本思路如上面分析一致,我們采用逐步試探的方式,先從一個(gè)方向往前走,能進(jìn)則進(jìn),不能進(jìn)則退,嘗試另外的路徑。首先我們來(lái)分析一下國(guó)際象棋的規(guī)則,這些規(guī)則能夠限制我們的前進(jìn),也就是我們前進(jìn)途中的障礙物。一個(gè)皇后q(x,y)能被滿(mǎn)足以下條件的皇后q(row,col)吃掉
1)x=row(在縱向不能有兩個(gè)皇后)
2)? y=col(橫向)
3)col + row = y+x;(斜向正方向)
4)? col - row = y-x;(斜向反方向)
遇到上述問(wèn)題之一的時(shí)候,說(shuō)明我們已經(jīng)遇到了障礙,不能繼續(xù)向前了。我們需要退回來(lái),嘗試其他路徑。
我們將棋盤(pán)看作是一個(gè)8*8的數(shù)組,這樣可以使用一種蠻干的思路去解決這個(gè)問(wèn)題,這樣我們就是在8*8=64個(gè)格子中取出8個(gè)的組合,C(64,80) = 4426165368,顯然這個(gè)數(shù)非常大,在蠻干的基礎(chǔ)上我們可以增加回溯,從第0列開(kāi)始,我們逐列進(jìn)行,從第0行到第7行找到一個(gè)不受任何已經(jīng)現(xiàn)有皇后攻擊的位置,而第五列,我們會(huì)發(fā)現(xiàn)找不到皇后的安全位置了,前面四列的擺放如下:
第五列的時(shí)候,擺放任何行都會(huì)上圖所示已經(jīng)存在的皇后的攻擊,這時(shí)候我們認(rèn)為我們撞了南墻了,是回頭的時(shí)候了,我們后退一列,將原來(lái)擺放在第四列的皇后(3,4)拿走,從(3,4)這個(gè)位置開(kāi)始,我們?cè)俚谒牧兄袑ふ蚁乱粋€(gè)安全位置為(7,4),再繼續(xù)到第五列,發(fā)現(xiàn)第五列仍然沒(méi)有安全位置,回溯到第四列,此時(shí)第四列也是一個(gè)死胡同了,我們?cè)倩厮莸降谌?#xff0c;這樣前進(jìn)幾步,回退一步,最終直到在第8列上找到一個(gè)安全位置(成功)或者第一列已經(jīng)是死胡同,但是第8列仍然沒(méi)有找到安全位置為止
總結(jié)一下,用回溯的方法解決8皇后問(wèn)題的步驟為:
1)從第一列開(kāi)始,為皇后找到安全位置,然后跳到下一列
2)如果在第n列出現(xiàn)死胡同,如果該列為第一列,棋局失敗,否則后退到上一列,在進(jìn)行回溯
3)如果在第8列上找到了安全位置,則棋局成功。
8個(gè)皇后都找到了安全位置代表棋局的成功,用一個(gè)長(zhǎng)度為8的整數(shù)數(shù)組queenList代表成功擺放的8個(gè)皇后,數(shù)組索引代表棋盤(pán)的col向量,而數(shù)組的值為棋盤(pán)的row向
量,所以(row,col)的皇后可以表示為(queenList[col],col),如上圖中的幾個(gè)皇后可表示為:
queenList[0] = 0;? queenList[1] = 3;?? queenList[2] = 1;? queenList[3] = 4;?? queenList = 2;
<span style="font-size:12px;">/* * Copyright (c) leo * All rights reserved. * filename: nQueens * summary : * version : 1.0 * author : leo * date : 8.12.2011 *問(wèn)題: * 在n*n (n=1 or n>=4 )的棋盤(pán)上放置n個(gè)皇后,如果在同一行,同一列,同一對(duì)角線(xiàn)上都不存在兩個(gè)皇后, * 那么這個(gè)棋盤(pán)格局就是n皇后的一個(gè)解。 *要求: * 找出n皇后的一組解即可,打印出放置滿(mǎn)足n皇后條件的棋子位置 */ #include<stdio.h> #include<math.h> #include<stdlib.h> #include<conio.h> #define N 8 //皇后數(shù)=棋盤(pán)行列數(shù) int a[N]; //a[i]為第i行皇后所在列 void show() //圖形化輸出 {int i;int p,q ;int b[N][N]={0};static t=1;printf("第%d個(gè)解為: ",t++);for(i=0;i<N;i++){b[i][a[i]]=1;printf("(%d,%d) ",i,a[i]);}printf("\n");for(p=0;p<N;p++){for(q=0;q<N;q++){if(b[p][q]==1)printf("●");elseprintf("○");}printf("\n");} } int check(int n) //檢查位置是否合法,滿(mǎn)足條件返回1,否則返回0 {int i;for(i=0;i<n;i++){if(a[i]==a[n]||fabs(n-i)==fabs(a[i]-a[n])) //at the same column or diagonal (對(duì)角線(xiàn))return 0;}return 1; } void put(int n) //主體部分,在第n行放置第n個(gè)皇后 {int i;if(n==N)return ;for(i=0;i<N;i++){a[n]=i;if(check(n)) //位置合法{if(n==N-1) //皇后全部放置完畢show();elseput(n+1);}} } int main () {put(0);return 0; }</span>
總結(jié)
以上是生活随笔為你收集整理的五大常用算法之回溯法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 542. 01 Matrix
- 下一篇: 状态栏显示时间代码