独粒钻石求解算法
想学习一下rust,就把上次的独粒钻石算法拿来练习了。 这次总结一下算法的剪枝问题。
深度搜索
空间复杂度优秀,但深度搜索时间复杂度太大。所以考虑剪枝。
1,记录已经得到的解的最小连步数量,搜索时超过这个数量就放弃搜索。
2,根据路径做hash来对重复路径剪枝效果很小。
开始做深度搜索时觉得剪枝手段不够,所以选择了广度搜索。
广度搜索
空间复杂度太大。如果没有有效剪枝,内存根本不够用。我的笔记本64GB内存,远远不够。剪枝考虑:
1,盘面hash剪枝。
给盘面做一个hash,每次搜索检查盘面重复则剪枝。 缺点是会剪掉最优解。剩下的解是否最优靠运气,一般都得不到。
2,对称剪枝。
对盘面做包括自身的8种对称变换,发现对称盘面也算作重复盘面。 这样同样可能剪掉最优解。因为到达同一个盘面可以有不同的路径。保留一个的时候,及时保留步数较小的一个也可能会剪掉最优解。
3,落点剪枝。
在对称剪枝基础上增加条件,把落子的坐标作为hash因子,追加到盘面hash后面,同样要做对称处理。这样剪枝虽然也可能剪掉最优解,但是至少会保留一个最优解。因为到达一个相同盘面并且落点相同,这之前相同步数的路径可能不同,但是步数相同。后续步数不会使这些步数相同的分支产生不同的步数。所以取一个最小步数的盘面就一定能保留一个最优解。但是肯定也可能剪掉最优解。 到此为止,已经可以得到最优解了。但是不是所有最优解。
4,路径hash。
为了得到所有最优解,需要保留相同盘面所有分支。所以给路径做了一个hash。用来识别重复路径。不重复的路径保留。 这样一下子增加了巨大的空间复杂度。前面也说了这个剪枝方案效果不大。 所以,结果就是内存不够了。我是完成了13级深度的搜索,14级深度的时候内存不够了的。 根据前面的经验,估计要到20级之后,空间复杂度才开始降低。所以现在的空间复杂度距离可用还有很远的距离。
其他方案思考
还有其他办法吗? 也许可以考虑深度和广度搜索结合使用。但是怎么结合待考虑。 剪枝办法中可以把最后的落点和方向一起考虑进去做hash,但是也就只是又苛刻了一点,仍然解决不了剪掉最优解的问题。
以上给自己记录一下,给有兴趣的朋友一个参考。 下面是rust的代码。谨供参考。说起来这个rust,刚开始学习使用真是不习惯。其他语言感觉相似性比较大,这个语言有些概念与其他语言差别有点大。 头一次写rust,估计很丑陋,大家将就着看。
use std::time::Instant;
use std::io::stdin;
use std::collections::{VecDeque, HashMap,HashSet};
use lazy_static::lazy_static;
//extern crate flame;
const DEBUG:bool=false;
//const DEBUG:bool=true;
const EMPTY: i8 = 0;
const PEG:i8 = 1;
//这个方向不能改,对称变换时按规律变换。
const UP:usize=0;
const LEFT:usize=1;
const DOWN:usize=2;
const RIGHT:usize=3;
#[derive(Clone,Debug )]
struct Board {
cells: [i8; 49], // 7x7 grid represented as a 1D array
depth: i8,
from:i8,
mid:i8,
to:i8,
move_count:i8,
hash_key:i64,
path_hash: [u8; 32],
}
impl Board {
fn new(cells: [i8; 49],depth:i8,from:i8,mid:i8,to:i8,move_count:i8,hash_key:i64 ,path_hash: [u8;32]) -> Self {
Board { cells , depth,from,mid,to,move_count,hash_key,path_hash}
}
fn print_path(&self) {
let hash = &self.path_hash;
// 遍历每个字节
//for i in 0..hash.len() {
for i in 1..=self.depth as usize{
// 取得当前字节的 ASCII 值
let byte = hash[i];
// 解析行坐标(前3位)
let row = (byte >> 5) & 0b111;
// 解析列坐标(中3位)
let col = (byte >> 2) & 0b111;
// 解析方向(后2位)
let direction = byte & 0b11;
// 打印结果
println!("Step {}: Row={}, Col={}, Direction={}", i , row, col, direction);
}
}
fn print(&self) {
for i in 0..7 {
for j in 0..7 {
let index = i * 7 + j;
print!("{:?} ", self.cells[index]);
}
println!();
}
println!("depth:{},from:{},mid:{},to:{},moveCount:{} ",self.depth,self.from,self.mid,self.to,self.move_count);
}
#[inline]
fn is_inboard(&self,row:i8, col:i8) ->bool {
// 检查是否在棋盘范围内,且不是角上的16个位置
let result:bool = row >= 0 && row < 7 && col >= 0 && col < 7 &&
(!((row == 0 || row == 1 || row == 5 || row == 6) && (col == 0 || col == 1 || col == 5 || col == 6)));
result
}
fn is_valid_move(&self, i:i8, j:i8, direction: usize) -> bool {
let index = i * 7 + j ;
let (from, mid, to, torow, tocol) = match direction {
UP => (index, index - 7, index - 14, i - 2, j ),
DOWN => (index, index + 7, index + 14, i + 2, j ),
LEFT => (index, index - 1, index - 2, i, j - 2),
RIGHT => (index, index + 1, index + 2, i, j + 2),
_ => return false, // Invalid direction
};
self.is_inboard(i,j) && self.is_inboard(torow,tocol)
&& self.cells[from as usize]==PEG && self.cells[mid as usize] == PEG && self.cells[to as usize] == EMPTY
}
fn apply_move(&mut self, i: usize, j: usize, direction:usize) {
let index:i8 = (i * 7 + j) as i8;
match direction {
UP => { self.from=index; self.mid=index-7; self.to=index-14; }
DOWN => { self.from=index; self.mid=index+7; self.to=index+14; }
LEFT => { self.from=index; self.mid=index-1; self.to=index-2; }
RIGHT => { self.from=index; self.mid=index+1; self.to=index+2; }
_ => {}
}
self.cells[self.from as usize] = EMPTY;
self.cells[self.mid as usize] = EMPTY;
self.cells[self.to as usize] = PEG;
}
#[inline]
fn is_solved(&self) -> bool {
self.depth == 31 && self.cells[24] == PEG
}
fn flip_hash(&self,mode:usize)->i64 {
let mut result: i64 =0;
//1.原始矩阵 (原点对称)IDENTITY·公式: M'=M
//2.水平翻转(水平轴对称)公式: M'= Mi,7-j-1
//3,垂直翻转(垂直轴对称)公式: M'= M7-i-1,j
//4.主对角线翻转(以主对角线为轴对称)公式: M' = Mj,i
//5.次对角翻转(以次对角线为轴对称)·公式:M'= M7-j-1,7-i-1
//6.顺时针旋转90度·公式: M'= M7-j-l,i
//7.逆时针旋转90度.公式: M'= Mj,7-i-1
//8.中心对称变换·CENTERAL: M'= M7-i-1,7-j-1
for i in 0..7{
for j in 0..7{
match mode {
IDENTITY => result = result.wrapping_shl(1).wrapping_add(self.cells[i * 7 + j ] as i64),
HORIZONTAL => result = result.wrapping_shl(1).wrapping_add(self.cells[i * 7 + (6 - j)] as i64),
VERTICAL => result = result.wrapping_shl(1).wrapping_add(self.cells[(6 - i) * 7 + j ] as i64),
R_SLASH => result = result.wrapping_shl(1).wrapping_add(self.cells[j * 7 + i ] as i64),
L_SLASH => result = result.wrapping_shl(1).wrapping_add(self.cells[(6 - j) * 7 + (6 - i)] as i64),
R_90 => result = result.wrapping_shl(1).wrapping_add(self.cells[(6 - j) * 7 + i ] as i64),
L_90 => result = result.wrapping_shl(1).wrapping_add(self.cells[j * 7 + (6 - i)] as i64),
CENTRAL => result = result.wrapping_shl(1).wrapping_add(self.cells[(6 - i) * 7 + (6 - j)] as i64),
_ => {}
}
// match mode {//直接取下标更快。get再match等慢。
// IDENTITY =>{ result = (result<<1) | self.cells[(i )*7+(j )] as i64 ; }
// HORIZONTAL =>{ result = (result<<1) | self.cells[(i )*7+(6-j)] as i64 ; }
// VERTICAL =>{ result = (result<<1) | self.cells[(6-i)*7+(j )] as i64 ; }
// R_SLASH =>{ result = (result<<1) | self.cells[(j )*7+(i )] as i64 ; }
// L_SLASH =>{ result = (result<<1) | self.cells[(6-j)*7+(6-i)] as i64 ; }
// R_90 =>{ result = (result<<1) | self.cells[(6-j)*7+(i )] as i64 ; }
// L_90 =>{ result = (result<<1) | self.cells[(j )*7+(6-i)] as i64 ; }
// CENTRAL =>{ result = (result<<1) | self.cells[(6-i)*7+(6-j)] as i64 ; }
// _=>{}
// }
}
}
//落子位置对称变换,合并到hash值中。
let i=self.to/7;
let j=self.to%7;
match mode {
IDENTITY =>{ result = result.wrapping_shl(1).wrapping_add (((i )*7+(j )) as i64) ; }
HORIZONTAL =>{ result = result.wrapping_shl(1).wrapping_add (((i )*7+(6-j)) as i64) ; }
VERTICAL =>{ result = result.wrapping_shl(1).wrapping_add (((6-i)*7+(j )) as i64) ; }
R_SLASH =>{ result = result.wrapping_shl(1).wrapping_add (((j )*7+(i )) as i64 ); }
L_SLASH =>{ result = result.wrapping_shl(1).wrapping_add (((6-j)*7+(6-i)) as i64 ); }
R_90 =>{ result = result.wrapping_shl(1).wrapping_add (((6-j)*7+(i )) as i64 ); }
L_90 =>{ result = result.wrapping_shl(1).wrapping_add (((j )*7+(6-i)) as i64 ); }
CENTRAL =>{ result = result.wrapping_shl(1).wrapping_add (((6-i)*7+(6-j)) as i64 ); }
_=>{}
}
result
}
fn path_hash(&mut self, row: usize, col: usize, direction:usize, mode:usize) -> [u8;32] {
//先把自身hash值补齐。其他模式的时候会重复做一次补齐。
self.path_hash[self.depth as usize]= ((row <<5) + (col <<2) +DIRTRANS[IDENTITY ][direction ] as usize) as u8;
if mode == IDENTITY {
return self.path_hash;
}
let mut result = [0 as u8;32];
// 遍历每个字节
//for i in 0..hash.len() {
for dth in 1..=self.depth as usize{
// 取得当前字节的 ASCII 值
let byte = self.path_hash[dth];
// 解析行坐标(前3位)
let i = (byte >> 5) & 0b111;
// 解析列坐标(中3位)
let j = (byte >> 2) & 0b111;
// 解析方向(后2位)
let dir = (byte & 0b11) as usize;
// 打印结果
match mode {
IDENTITY => {result[dth] = ((i <<5) + (j <<2) +DIRTRANS[IDENTITY ][dir] as u8) as u8;}
HORIZONTAL => {result[dth] = ((i <<5) + ((6 - j)<<2) +DIRTRANS[HORIZONTAL][dir] as u8) as u8;}
VERTICAL => {result[dth] = (((6 - i)<<5) + (j <<2) +DIRTRANS[VERTICAL ][dir] as u8) as u8;}
R_SLASH => {result[dth] = ((j <<5) + (i <<2) +DIRTRANS[R_SLASH ][dir] as u8) as u8;}
L_SLASH => {result[dth] = (((6 - j)<<5) + ((6 - i)<<2) +DIRTRANS[L_SLASH ][dir] as u8) as u8;}
R_90 => {result[dth] = (((6 - j)<<5) + (i <<2) +DIRTRANS[R_90 ][dir] as u8) as u8;}
L_90 => {result[dth] = ((j <<5) + ((6 - i)<<2) +DIRTRANS[L_90 ][dir] as u8) as u8;}
CENTRAL => {result[dth] = (((6 - i)<<5) + ((6 - j)<<2) +DIRTRANS[CENTRAL ][dir] as u8) as u8;}
_=>{}
}
}
result
}
}
const IDENTITY:usize =0;
const CENTRAL:usize=1;
const HORIZONTAL:usize=2;
const VERTICAL:usize=3;
const R_SLASH:usize=4;
const L_SLASH:usize=5;
const R_90:usize=6;
const L_90:usize=7;
fn peg_solitaire_bfs(initial_board: Board) {
let mut board_cnt:i64 = 1;
let mut queue = VecDeque::new();
let mut board_hashmap:HashMap
let mut path_hashset:HashSet<[u8; 32]> = HashSet::new();//HashMap::
if DEBUG {
let mut buf = String::from("");
initial_board.print();
initial_board.print_path();
let _=stdin().read_line(&mut buf);
}
queue.push_back(initial_board.clone());
board_hashmap.insert(initial_board.hash_key, initial_board.move_count);
path_hashset.insert(initial_board.path_hash);
let start_time = Instant::now();
let mut last_depth:i8 =0;
while let Some(current_board) = queue.pop_front() {
if current_board.depth+1 > last_depth {
last_depth=current_board.depth+1;
println!("Depth: {},boardCnt:{},queue_len:{},board_hash:{},path_hash:{}, Elapsed Time: {:?}", current_board.depth+1,board_cnt,queue.len()+1,board_hashmap.len(),path_hashset.len(), start_time.elapsed());
}
if current_board.is_solved() {
println!("Solution found in {} moves:", current_board.depth);
current_board.print();
current_board.print_path();
continue;
}
let self_hash_key = current_board.hash_key;
if let Some(last_move_count) = board_hashmap.get(&self_hash_key){
if *last_move_count < current_board.move_count {
//出队列的旧盘面步数大,清除路径hash,保留盘面hash,继续出队列。
//path_hashset.remove(¤t_board.path_hash[IDENTITY as usize]);
path_hashset.remove(¤t_board.path_hash);
continue;
}
else {
board_hashmap.remove(&self_hash_key);
path_hashset.remove(¤t_board.path_hash);
}
}
else {
path_hashset.remove(¤t_board.path_hash);
}
for i in 0..7 {//2D row
for j in 0..7 {//2D col
let index = i * 7 + j;//1D index
if current_board.cells[index] == PEG {//起始位有棋子。
for direction in 0..4 as usize { //4个方向:0UP, 1DOWN, 2LEFT, 3RIGHT
if current_board.is_valid_move(i as i8, j as i8, direction) {//是否有效棋步
let mut new_board = Board{ depth: current_board.depth+1, ..current_board};//拷贝一个新盘面,初始化深度。
board_cnt +=1;//生成盘面数累计。
new_board.apply_move(i, j, direction);//新盘面走一步棋。
if current_board.to !=new_board.from{//走一步之后,判断不是连步
new_board.move_count +=1;//步数加1 .连步不用加,用前一个盘面的步数。
}
if DEBUG {
new_board.print();
new_board.print_path();
}
let mut board_exist:bool=false;
let mut comp=2; //move_count比较:0 新的小,有效,1,相等 ,path新的有效,1,新的大。无效
let mut path_exist=false;
let new_move_count = new_board.move_count;
let mut new_board_hash:i64;
let mut new_path_hash:[u8;32];//[0;32];
for mode in 0..8 as usize {//8种对称变换的hash值
new_board_hash = new_board.flip_hash(mode);
new_path_hash=new_board.path_hash(i,j,direction,mode);
if mode == IDENTITY {//盘面自身的hash。IDENTITY=0,是第一个,一定有值。
new_board.hash_key=new_board_hash;
}
if let Some(value) = board_hashmap.get(&new_board_hash){
board_exist=true;
if *value > new_move_count { //旧棋步大,新棋步有效。
board_hashmap.remove(&new_board_hash);//新旧盘面由于对称关系,hash可能不同。要换成新的。
comp=0;
break;
}
else if *value == new_move_count {
comp=1;
if path_hashset.contains(&new_path_hash) {
path_exist=true;
break;
}
}
}
}
if !board_exist {
board_hashmap.insert(new_board.hash_key,new_move_count);
path_hashset.insert(new_board.path_hash);
queue.push_back(new_board);
}
else {
if comp==0 {
board_hashmap.insert(new_board.hash_key,new_move_count);
path_hashset.insert(new_board.path_hash);
queue.push_back(new_board);
}
else if comp==1 {
if !path_exist {
path_hashset.insert(new_board.path_hash);
queue.push_back(new_board);
}
}
}
if DEBUG {
let mut buf = String::from("");
let _ = stdin().read_line(&mut buf);
}
}
}
}
}
}
}
println!("No solution found.");
}
fn main() {
let initial_board = Board::new([
EMPTY, EMPTY, PEG, PEG, PEG, EMPTY, EMPTY,
EMPTY, EMPTY, PEG, PEG, PEG, EMPTY, EMPTY,
PEG, PEG, PEG, PEG, PEG, PEG, PEG,
PEG, PEG, PEG, EMPTY, PEG, PEG, PEG,
PEG, PEG, PEG, PEG, PEG, PEG, PEG,
EMPTY, EMPTY, PEG, PEG, PEG, EMPTY, EMPTY,
EMPTY, EMPTY, PEG, PEG, PEG, EMPTY, EMPTY,
],0,0,0,0,0,0,
[0;32 ]);
peg_solitaire_bfs(initial_board);
}
//4个方向经过8种对称变换后的对应方向。
lazy_static! {
static ref DIRTRANS: [[usize; 4]; 8] = [
[UP,LEFT,DOWN,RIGHT],[DOWN,RIGHT,UP,LEFT],
[UP,RIGHT,DOWN,LEFT],[DOWN,LEFT,UP,RIGHT],
[LEFT,UP,RIGHT,DOWN],[RIGHT,DOWN,LEFT,UP],
[LEFT,DOWN,RIGHT,UP],[RIGHT,UP,LEFT,DOWN]];
}
一个没有结果的结果: cargo run --release Finished release [optimized + debuginfo] target(s) in 0.04s Running target\release\pegsolitaire.exe Depth: 1,boardCnt:1,queue_len:1,board_hash:1,path_hash:1, Elapsed Time: 200ns Depth: 2,boardCnt:5,queue_len:1,board_hash:1,path_hash:1, Elapsed Time: 143.9µs Depth: 3,boardCnt:8,queue_len:2,board_hash:2,path_hash:2, Elapsed Time: 410.9µs Depth: 4,boardCnt:18,queue_len:8,board_hash:8,path_hash:8, Elapsed Time: 683.7µs Depth: 5,boardCnt:69,queue_len:50,board_hash:50,path_hash:50, Elapsed Time: 864.7µs Depth: 6,boardCnt:439,queue_len:369,board_hash:276,path_hash:369, Elapsed Time: 1.1263ms Depth: 7,boardCnt:3463,queue_len:2963,board_hash:1407,path_hash:2963, Elapsed Time: 2.7169ms Depth: 8,boardCnt:29687,queue_len:24643,board_hash:6482,path_hash:24643, Elapsed Time: 15.2742ms Depth: 9,boardCnt:255362,queue_len:199847,board_hash:26996,path_hash:199847, Elapsed Time: 121.9822ms Depth: 10,boardCnt:2144082,queue_len:1563498,board_hash:101542,path_hash:1563498, Elapsed Time: 1.0280991s Depth: 11,boardCnt:17201927,queue_len:11500640,board_hash:337763,path_hash:11500640, Elapsed Time: 9.6047411s Depth: 12,boardCnt:128604545,queue_len:77792766,board_hash:989401,path_hash:77792766, Elapsed Time: 88.1998937s Depth: 13,boardCnt:879659929,queue_len:474342130,board_hash:2546636,path_hash:474342130, Elapsed Time: 836.4782469s memory allocation of 103079215104 bytes failed error: process didn’t exit successfully: target\release\pegsolitaire.exe (exit code: 0xc0000409, STATUS_STACK_BUFFER_OVERRUN)
相关文章
发表评论