回溯算法:从电影蝴蝶效应中学习回溯算法的核心思想

回溯算法:从电影<蝴蝶效应>中学习回溯算法的核心思想

数独、八皇后、0-1背包、图的着色、旅行商问题、全排列问题都能用到

理解“回溯算法”

回溯的思想,类似枚举搜索,枚举所有的解,找到满足期望的解,为了有规律枚举所有可能的解,把问题求解的过程分为多个阶段,每个阶段,都会面对一个岔路口,先随意选一条路,当发现这条路不通的时候(不满足期望)就回退到上一个岔路口,另选一种。

八皇后问题:

有一个8x8棋盘,往里面放8个棋子(皇后),每个棋子所在行、列、对角线都不能有另外一个棋子

把这个问题划分为8个阶段,依次将8个棋子放到第一行、第二行、……、第八行,在放置的过程中,不停的检查是否满足规则,如果满足就跳到下一行继续放置棋子,不满足就换一种放法尝试

int[] result = new int[8];//全局或成员变量,下标表示行,值表示queen存储在哪一列
public void cal8queens(int row){   //调用方式:cal8queen(0);
	if(row == 8){   //8个棋子都放置好了打印结果
		printQueens(result);
		return;    //8个棋子都放好了,已经没法再往下递归了,所以就return
	}
	for(int column = 0 ; column < 8 ; ++column){  //每一行都有8种放法
		if(isOk(row,column)){  //有些放法不满足要求
			result[row] = column;   //第row行的棋子放到了column列
			cal8queens(row+1);   //考察下一行
		}
	}
}

private boolean isOk(int row , int column){  //判断row行column列放置是否合适
	int leftup = column - 1 , rightup = column + 1;
	for(int i = row - 1; i >= 0 ; --i){  //逐行往上考察每一行
		if(result[i] == column) return false;  //第i行的column列有棋子吗?
		if(leftup >= 0){   //考察左上对角线:第i行leftup列有棋子吗?
			if(result[i] == leftup) return false;
		}
		if(rightup < 8 ){  //考察右上对角线:第i行rightup列有棋子吗?
			if(result[i] == rightup)  return false;
		}
		--leftup ; ++rightup;
	}
	return true;
}

private void printQueens(int[] result){   //打印出一个二维矩阵
	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();
}

两个回溯算法的经典应用

1. 0-1背包

有一个背包,背包总的承载重量是Wkg,我们有n个物品,每个物品的重量不等,且不可分割,期望选择几件物品装载到背包中,在不超过背包所能装载重量的前提下,如何让背包物品的总重量最大?

对于n个物品来说,总的装法有2^n种,去掉总重量最接近Wkg,从剩下的装法中选择总重量最接近Wkg的,如何才能不重复穷举这2^n种装法呢?

把物品依次排列,整个问题就分解成了n个阶段,每个阶段对应一个物品怎么选择,先对第一个物品进行处理,选择装进去还是不装,再递归处理剩下的物品

如果发现已经选择的物品重量超过了Wkg,停止继续探测剩下的物品

public int maxW = Integer.MIN_VALUE;//存储背包中物品总重量的最大值
//cw表示当前已经装进去的物品的重量和; i表示考察到哪个物品了;
//w背包重量 ; items表示每个物品的重量;n表示物品个数
//假设背包可承受重量100,物品个数10,物品重量存储在数组a中,那可以这样调用函数:
//f(0,0,a,10,100)
public void f(int i , int cw, int[] items , int n , int w){
	if(cw == w || i == n ){   // cw == w表示装满了;i== n表示已经考察完所有的物品
		if(cw  >  maxW) maxW = cw;
		return;
	}
	f(i+1 , cw , items , n ,w);
	if(cw + items[i] <= w){   //已经超过可以背包承受的重量的时候,就不要再装了
	f(i+1 , cw + items[i] , items ,n , w);
	}
}

2. 正则表达式

正则表达式中,最重要的是通配符,假设正则表达式中只包含 *?两个通配符,其中,* 匹配任意多个(大于等于0个)任意字符,?匹配零个或者一个任意字符,如何通过回溯算法,判断一个给定的文本,能否跟给定的正则表达式匹配?

依次考察正则表达式中的每个字符,当时非通配符时,直接跟文本的字符进行匹配,如果相同,继续往下处理,如果不同,则回溯

如果遇到特殊字符的时候,比如*有多种匹配方案,可以匹配任意个文本串中的字符,先随意选择一种匹配方案,继续考察剩下的字符

扫描二维码关注公众号,回复: 9867866 查看本文章
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] == "?"){    //?匹配0个或者1个字符
			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);
		}
	}
}
发布了75 篇原创文章 · 获赞 9 · 访问量 9182

猜你喜欢

转载自blog.csdn.net/ywangjiyl/article/details/104717884