用STL实现先深搜索及先宽搜索——数独(sudoku)例子(2)

用STL实现先深搜索及先宽搜索
——数独 (sudoku) 例子 (2)
前面我们用一个游戏——数独sudoku,实作出了一个简单(但效率较差)的解法,验证了我们的DFS和BFS算法。为简单起见,我们采用了最简单的解法而暂不考虑效率。这里,我们尝试改进一下数独问题解法的效率,并将新的解法与原来的解法进行一个效率的对比。
从DFS/BFS算法中可以看出,搜索算法的执行效率主要取决于问题状态空间树的大小,如果状态空间树每层的分支较少,则树的结点也会较少,这样搜索就可以更快结束。所以,我的基本想法就是,尽量减少状态空间树中每层的分支数目。
具体到数独游戏,我们原来的解法是:在二维数组中扫描到第一个未填数字的空格,就以这个空格为基础来生成下一层状态结点分支,而根本没有考虑过这个空格是否最佳的生成点。这样自然会导致状态空间树的最高几层分支过多,结点数有可能急速增加,从而增长了搜索时间。
我的想法是,不要找到一个空格就立即用于生成下一层状态结点,而是对目前状态中的所有空格都计算出其可能的分支数目,然后以分支数目最少的一个空格为生成点来生成下一层状态结点。这样,状态空间树的最高几层的分支就会相对较少,从而降低搜索的时间。
显然,这只需要对原来的SudokuState::nextStep()成员函数进行改写即可,如下:
void SudokuState::nextStep(vector<SudokuState>& vs) const
{
    SudokuState newState;
    bool pos[SUDOKU_DIMS], bestPos[SUDOKU_DIMS];
    int best = SUDOKU_DIMS+1, bestRow, bestCol;
    for (int row = 0; row < SUDOKU_DIMS; ++row)
    {
        for (int col = 0; col < SUDOKU_DIMS; ++col)
        {
            if (data_[row][col] == 0) // Space
            {
                fill_n(pos, SUDOKU_DIMS, true);
               for (int k = 0; k < SUDOKU_DIMS; ++k)
                {
                    checkValue(pos, data_[k][col]);
                    checkValue(pos, data_[row][k]);
                }
                int rs = row - (row % SUDOKU_ROWS);
                int cs = col - (col % SUDOKU_COLS);
                for (int i = 0; i < SUDOKU_ROWS; ++i)
                {
                    for (int j = 0; j < SUDOKU_COLS; ++j)
                    {
                        checkValue(pos, data_[rs+i][cs+j]);
                    }
                }
                int s = count(pos, pos+SUDOKU_DIMS, true);
                if (s == 0) // 如果有一个空格没有可以填的值
                {
                    return;
                }
                if (best > s)
                {
                   best = s;
                    copy(pos, pos+SUDOKU_DIMS, bestPos);
                    bestRow = row;
                    bestCol = col;
                }
            }
        }
    }
    for (int k = 1; k <= SUDOKU_DIMS; ++k)
    {
        if (bestPos[k-1]) // a possible value
        {
            newState = *this;
            newState.data_[bestRow][bestCol] = k;
            vs.push_back(newState);
        }
    }
}
 
与原来的函数相比,多了十几行,增加了几个局部变量,包括三个int和一个bool数组。这些新增的变量用于保存找到的分支最少的空格的一些相关数据,分别是空格所在行、列,分支数量以及可选的数字。其中有一点要注意的是,在循环中如果发现有一个空格没有可填入的数字,则说明当前状态是一个无解的分支状态,可以立即返回。
经测试,新的解法可以正确地用于DFS和BFS算法,并找到了正确答案,速度也有了大幅提高。在我的机器上进行测试结果(剔除I/O时间)如下:
 
DFS (one answer)
BFS (one answer)
DFS (all answers)
BFS (all answers)
原来的解法
3.5ms
5.8ms
8.3ms
8.3ms
优化的解法
1.6ms
2.3ms
2.2ms
2.3ms
由于我只使用了一个数独题目进行测试,所以结果并不具有广泛代表性,但也可以在一定程度上说明新的解法已经有了明显的速度提高。
当然,这个解法还可以进一步优化,你如果有兴趣,可以自己试着写一下,也欢迎你把结果反馈给我。
 
关于数独问题的讨论我想就到此为止了,我们回过头来看看我们的DFS/BFS算法,其实它还有很多局限。你可能已经注意到了,它只适用于一些特殊的问题,即这些问题的状态空间树中的结点不能有重复。我们还是以原来的图来说明一下:
我们想象一下,如果状态空间树中的结点E与结点A是相同的状态,那么会有什么结果?我们的DFS/BFS算法将会陷入一个无限循环!幸好,数独问题恰好不会出现这样的状况,但是象下象棋、推箱子等问题,都会存在这个问题,一个问题状态在几步之后可能会回到以前出现过的状态。 如何解决这个问题?我们将在以后继续讨论。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值