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