【独粒钻石求解算法-rust实现】

本文围绕独粒钻石求解算法的剪枝问题展开。深度搜索空间复杂度优但时间复杂度大,采用记录最小连步数量和路径hash剪枝。广度搜索空间复杂度大,有盘面hash、对称、落点和路径hash等剪枝方法,但都可能剪掉最优解。还考虑了深度与广度结合等其他方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

独粒钻石求解算法

想学习一下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<i64,i8> = HashMap::new();//HashMap::<i64, i8>();
    let mut path_hashset:HashSet<[u8; 32]> = HashSet::new();//HashMap::<i64, i8>();
    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(&current_board.path_hash[IDENTITY as usize]);
                path_hashset.remove(&current_board.path_hash);
                continue;
            }
            else {
                board_hashmap.remove(&self_hash_key);
                path_hashset.remove(&current_board.path_hash);
            }
        }
        else {
            path_hashset.remove(&current_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)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值