深度优先搜索利用的是回溯算法思想。
回溯算法大部分情况下都是用来解决广义的搜索问题:从一组可能的解中,选出一个满足要求的解。
回溯算法非常适合用递归来实现,在实现的过程中,剪枝操作是提高回溯效率的一种技巧,利用剪枝,并不需要穷举搜索所有的情况,从未提高搜索效率。
回溯算法理解
对比贪心算法,在每次面对岔路口的时候,都做出看起来最优的选择,期望这一组选择是最终的最优解。但是贪心算法并不一定能得到最优解。
回溯的处理思想,类似枚举搜索。枚举所有的解,找到满足期望的解。为了有规律的枚举所有可能的解,避免遗漏和重复,把问题求解的过程分为多个阶段。每个阶段都有会面对一个岔路口,先随意选一条路走,当发现这条路走不通的时候(不符合期望的解),就退回到上一个岔路口,另选一种走法继续走。
回溯算法应用
八皇后问题1
有一个8X8的棋盘,希望往里放8个棋子(皇后),每个棋子所在的行、列、对角线都不能有另一个棋子。
把这个问题分成8个阶段,依次将8个棋子放到第一行、第二行、第三行…第八行。在放置的过程中,不停地检查当前放法,是否满足要求。如果满足,则跳到下一行继续放置棋子;如果不满足,那就再换一种放法,继续尝试。2
八皇后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| int[] result = new int[8];
int count = 0;
public void cal8queens(int row) { if(row == 8) { printQueens(result); return; } for(int column = 0; column < 8; ++column) { if(isOk(row,column)) { result[row] = column; cal8queens(row + 1); } } }
private boolean isOk(int row, int column) { int leftup = column - 1; int rightup = column + 1; for(int i = row - 1; i >= 0; --i) { if(result[i] == column) return false; if(leftup >= 0) { if(result[i] == leftup) return false; } if(rightup < 8) { if(result[i] == rightup) return false; } --leftup; ++rightup; } return true; }
private void printQueens(int[] result) { System.out.println("第" + ++count + "种解法:"); for(int row = 0; row < 8; ++row) { for(int column = 0; column < 8; ++column) { if(result[row] == column) System.out.print("Q "); else System.out.print("* "); } System.out.println(); } System.out.println(); }
|
0-1背包
动态规划
回溯算法
一个背包,背包总的承载重量是Wkg。现在有n个物品,每个物品的重量不等,并且不可分割。期望选择几件物品,装载到背包中,在不超过背包所能装载重量的前提下,让背包中物品的总重量最大。
对于每个物品来说,都有两种选择,装进背包或者不装进背包。对于n各物品来说,总的装法就有2n种,去掉总重量超过Wkg的,从剩下的装法中选择总重量最接近Wkg的。
把物品依次排列,整个问题就分解成了n个阶段,每个阶段对应一个物品怎么选择,先对第一个物品进行处理,选择装进去或者不装进去,然后再递归地处理剩下的物品。
0-1背包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public int maxW = Integer.MIN_VALUE;
public void f01(int i, int cw, int[] items, int n, int w) { if(cw == w || i == n) { if(cw > maxW) maxW = cw; return; } f01(i+1,cw,items,n,w); if(cw + items[i] <= w) { f01(i+1,cw+items[i],items,n,w); } }
|
变体:每个物品重量不同,价值不同,在不超过背包重量的情况下,让背包中的总价值最大。
正则表达式
背景假设,正则表达式中只包含“”和“?”这两种通配符,其中“”匹配任意多个(>=0个)任意字符,“?”匹配另个或者一个任意字符。
依次考察正则表达式中的每个字符,当是非通配符时,就直接跟文本的字符进行匹配,如果相同,则继续往下处理;如果不同,则回溯。
如果遇到特殊字符的时候,也就是岔路口,“*”,先随意的选择一种匹配方案,然后继续考察剩下的字符,如果中途发现无法继续匹配下去,就回溯到岔路口,重新选择一种匹配方案,然后再继续匹配剩下的字符。
正则表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class Pattern { private boolean matched = false; private char[] pattern; private int plen;
public Pattern(char[] pattern,int plen) { this.pattern = pattern; this.plen = plen; }
public boolean match(char[] text, int tlen) { matched = false; rmatch(0,0,text,tlen); return matched; }
private void rmatch(int ti, int pj, char[] text, int tlen) { if(matched) return; if(pj == plen) { if(ti == tlen) matched = true; return; }
if(pattern[pj] == '*') { for(int k = 0; k <= tlen-ti; ++k) { rmatch(ti+k,pj+1,text,tlen); } } else if(pattern[pj] == '?') { rmatch(ti,pj+1,text,tlen); rmatch(ti+1,pj+1,text,tlen); } else if(ti < tlen && pattern[pj] == text[ti]) { rmatch(ti+1,pj+1,text,tlen); } } }
|
参考