剪枝的实现和特性

初级搜索
  1. 朴素搜索(暴力搜索,傻搜)
  2. 优化方式:不重复(fibonacci),剪枝(生成括号问题)
  3. 搜索方向: DFS(depth first search深度优先搜索,利用栈), BFS(breath first search,广度优先搜索,利用队列)
高级搜索

双向搜索:从起点和终点分别做一个广度优先,然后在中间相遇,时间更快些
启发式搜索(优先级搜索,A*算法):不再用栈或者队列,这种先入先出或者先入后出的形式,而是用优先队列,节点按照优先级
在这里插入图片描述

高级搜索
剪枝

比如零钱置换的状态树,进行状态树搜索的时候,如果发现这个分支已经被搜索计算过了,那么就暂存在缓存中,这个分支就可以剪掉,不需要再次进行计算,当然有些时候是某个分支的不够好,比较差的分支或者是次优分支,也可以剪掉
三子棋:两个人玩,一个人走O一个人走X,最后看哪个是先连在一起的,可以是横,竖,斜,把整个状态树都遍历过一遍,基本上属于开天眼,不败状态,关键在于状态树的深度能否搞定,有没有好办法去剪枝
在这里插入图片描述
围棋比国际象棋要大不少,还有每一层的分支个数也非常的多,原因就是每次在西洋棋上面动的步数是相对有限的,比如你只能动卒,或者马的话只能走日,象的话只能走斜,但是围棋是不一样的,围棋只要是空地都可以落子,其实没有明显的限制,而且可以落子的选择性非常多,因为这个原因,围棋的状态树来自于AlphaGo的文章,状态树是异常复杂的,以至于用简单朴素的剪枝搜索无法简单的处理它的状态树找到最优的情况,1它cover不了这么深的深度, 2分支太多之后,直接把搜索空间打爆了,因为这个原因,直到最近几年,才由深度学习的一种方法解决
用深度学习的办法也是同样的思路,在这个状态树中进行更加有效的评估,哪个分支更好,更加及时的给剪枝出去,虽然用的是深度学习但整个思想也是跟之前的思想是异曲同工的.人类的话都是通过棋感,比如黑棋放在那里容易被白旗围剿,或者圈住的地盘不够,半感性,半理性的方法.而用神经网络,只要输入一个棋盘进去,就能够帮你估价白子胜算多少,这个地方对白旗更优还是黑棋更 优 给你进行引导和剪枝
AlphaZero Explained
人脑的问题是 1.回溯的深度不够,因为短期记忆比较模糊,经常回溯一两层就不太记得.2 人类比较懒,当多回溯几步之后特别累,自己就会算了,就不再想了
机器就擅长做重复的事情,递归特别快,记忆性好.
剪枝本身就是递归和分治,只是不断的进行试错环节
DP解决括号生成问题.点击题解,最简单易懂的动态规划

八皇后代码

八皇后问题,所谓的用columns,撇,捺这种方式进行判重,本质思想就是剪枝,只要是columns被占了或者撇,捺被占了,就马上进行剪枝,这个位置不能再填皇后了,不然的话,就会在每一行都加,最后再判断有没有皇后互相吃对方,所谓的用撇,捺,columns和不同的set来进行剪枝

def solveNQueens(self, n):
	if n < 1: return []
	self.result = []
	self.cols = set();self.pie = set(); self.na = set()
	self.DFS(n, 0, [])
	return self._generate_result(n)

def DFS(self, n, row, cur_state):
	#recursion terminator
	if row >= n:
	self.result.append(cur_state)
	return
for col in range(n):
	if col in self.cols or row + col in self.pie or row - col in self.na:
		#go die!
		continue
		#update the flags   把皇后占的位置设置好
		self.cols.add(col)
		self.pie.add(row + col)
		self.na.add(row - col)

		# 进行下一层
		self.DFS(n, row + 1, cur_state + [col])

		# 下一层完之后恢复之前的状态
		self.cols.remove(col)
		self.pie.remove(row + col)
		self.na.remove(row - col)

非位运算解决八皇后问题最漂亮的代码

class Solution(object):
	def solveNQueens(self, n):
		def DFS(queens, xy_dif, xy_sum):
			p = len(queens)
			if p == n:
				result.append(queens)
			    return None
			for q in range(n):
				if q not in queens and p-q not in xy_dif and p+q not in xy_sum:
					DFS(queens+[q], xy_dif+[p-q], xy_sum+[p+q])
		result = []
		DFS([], [], [])
		return [ ["."*i + "Q" + "."*(n-i+1) for i in sol] for sol in result]
有效的数独

思想,每个位置可以放1,2,3,4,5,6,7,8,9关键是否合法,所谓的合法就是看横竖以及所在的33的块,不会出现重复数字
1 暴力法(蛮力法),写循环判断,最关键就是要尽早的把不用的枝条给剪掉,这里需要三个set分别来记录行哪些数字被占,列哪些数字被占,以及3
3块哪些数字被占,就不断的递归和回溯这里方块的索引= (行 / 3) * 3 + 列 / 3, 其中/表示整数除法

在这里插入图片描述
在这里插入图片描述

public void solveSudoku(char[][] board) {
	// 首先判断棋盘是否合法,合法就调solve来进行解决
	if (board == null || board.length == 0) return false;
	return solve(board);
}
public boolean solve(char[][] board) {
	// 遍历整个棋盘,对于棋盘有空位的地方填1到9都尝试地往里面填,每次填完之后就判断整个棋盘是否合法
	// 如果合法就填上c,再递归调用solve,如果solve能解决的话return true就行了,不然就把状态树给恢复
	for (int i = 0; i < board.length; i++) {
		for (int j = 0; j < board[0].length; j++) {
			if (board[i][j] == '.') {
				for(char c = '1'; c <= '9'; c++) {
					if(isValid(board, i, j, c)) {
						board[i][j] = c;
						if (solve(board)) return true;
						else
							board[i][j] = '.';
					}
				}
				return false; //哪个都不行
			}
		}
	}
	return true;// 整个棋盘都完了
}

// 判断合法性(整个列,行,block是否没有重复的元素)
private boolean isValid(char[][] board, int row, int col, char c) {
	for (int i = 0; i < 9; i++) {
		if (board[i][col] != '.' && board[i][col] == c) return false;
		if (board[row][i] != '.' && board[row][i] == c) return false;
		if (board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] != '.' && board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c) return false;
	}
	return true;
}
public void solveSudoku(char[][] board) {
	dfs(board, 0);
}
private boolean dfs(char[][] board, int d) {
 	// 总共有9 * 9 = 81个格子,每个格子填1到9,递归总共有81层
	if (d == 81) return true;
	int i = d/9, j = d%9;
	// 如果当前坐标的值不是.的话, 说明这个格子已经被占了,直接下一层
	if (board[i][j] != '.') return dfs(board, d+1);
	// 如果当前坐标的值是.的话,用flag就开始validate,从而更新flag里面的值,flag值就是存储1到9是否有重复的
	// 如果validate通过,接下来就尝试在board,i,j填1到9的值,继续走dfs
	boolean[] flag = new boolean[10];
	validate(board, i, j, flag);
	for (int k = 1; k <= 9; k++) {
		if (flag[k]) {
			board[i][j] = (char)('0' + k);
			if (dfs(board, d+1)) return true;
		}
	}
	board[i][j] = '.'; // 恢复到空的,才能继续回溯
	return false;
}

private void validate(char[][] board, int i, int j, boolean[] flag) {
	Arrays.fill(flag, true);
	for (int k = 0; k < 0; k++) {
		if (board[i][k] != '.') flag[board[i][k] - '0'] = false;
		if (board[k][j] != '.') flag[board[k][j] - '0'] = false;
		int r = i / 3 * 3 + k / 3;
		int c = j / 3 * 3 + k % 3;
		if (board[r][c] != '.') flag[board[r][c] - '0'] = false;
	}
}
def solveSudoKu(self, board: List[List[str]]) -> None:
#这里是一个set数组,因为它列有9列,行是9行,每一行,每列,block也是9个都出现123456789不重复
#所以就是9个数组,每个数组里面是一个set,set里面只能存在1到9且不重复
	row = [set(range(1, 10)) for _ in range(9)] # 行剩余可用数
	col = [set(range(1, 10)) for _ in range(9)] # 列剩余可用数
	block = [set(range(1, 10)) for _ in range(9)] # 块剩余可用数字
	empty = [] # 收集需填数位置
	#整个当前的棋盘初始化状态哪些合法哪些不合法都记录且把空格中也筛选出来了全部放在empty中
	for i in range(9):
		for j in range(9):
			if board[i][j] != '.': # 更新可用数字, 如果当前不为.说明已经被占了,就去更新之前的行列和block
				val = int(board[i][j])
				row[i].remove(val)
				col[j].remove(val)
				block[(i // 3) * 3 + j // 3].remove(val)
			else:
				empty.append((i, j)) #不然就把空格子存起来, 预处理得到空行的位置(越是格子较少的格子可能先填或者先回溯)
				
		def backtrack(iter=0): #iter的写法是相等于比较高大上的一种写法,遍历之前empty数组,因为所有的empty位置要填东西
			if iter == len(empty): # 处理完empty代表找到了答案
				return True
			i, j = empty[iter] #从empty中拿值
			b = (i // 3) * 3 + j // 3 #计算出block
			for val in row[i] & col[j] & block[b]: #从i,j,block数组里面取得1到9里面
				row[i].remove(val)         #结果就是这里面全部都是空的,学习和借鉴下
				col[j].remove(val)
				block[b].remove(val)
				board[i][j] = str(val)  #还未填的值尝试着加进去 ,加进去之后再进行递归调用
				if backtrack(iter + 1):
					return True         #能解决return true,不能解决就恢复这一层状态, 
				row[i].add(val) # 回溯
				col[j].add(val)
				block[b].add(val)
			return False
		backtrack()
			
			
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值