独粒钻石求解算法

想学习一下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 = HashMap::new();//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)

相关文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: