用STL实现DFS/BFS算法
——使用 boost.Multi_Index 容器
花了几天时间熟悉了一下boost的Multi_Index容器,觉得确实是个很不错的东西。现在准备用它来替换DFS/BFS算法中的list容器以及查重所用的set或hash_set容器。
在前面的DFS/BFS版本中,我们使用了list容器来保存在搜索过程中生成的所有状态结点,并使用set或hash_set,还有vector容器来再保存另一份状态以实现查重。无论是哪一种查重方式,都需要对所有生成的状态结点另外保存一份拷贝,这样无疑浪费了一倍的内存空间,也多耗费了一倍的状态结点对象构造的时间。
为了节省这些浪费掉的空间和时间,我们应该只在容器中保存一份状态结点,而同时在该容器中实现查重。这正是boost.Multi_index容器提供的功能。关于multi_index_container的详细介绍请参见boost的在线文档,这里不再罗嗦。
你也许还记得,在前面的DFS/BFS版本中,我们提供了四种查重的策略,分别为NoCheckDup、SequenceCheckDup、OrderCheckDup和HashCheckDup,它们分别对应于不使用容器、使用vector容器、使用set容器和使用hash_set容器,分别采用了不查找、线性查找、二分查找和hash查找的算法。而multi_index_container容器只提供了类似于set和hash_set的二分查找和hash查找功能,不提供类似于vector的线性查找功能。所以,我们只有把SequenceCheckDup去掉了,只保留了其余三个。
在实现时,我的方法是,提供三个分别代表不查重、二分查重和hash查重的类型NoCheckDup、OrderCheckDup和HashCheckDup,我们称它们为查重型别。然后在StateSpaceTreeSearch类中定义嵌套类模板Container,Container分别对三个查重型别进行偏特化,分别用typedef指定对应的容器如下:
查重型别
对应的容器
NoCheckDup
list<S>
OrderCheckDup
multi_index_container<S, indexed_by< sequenced<>, ordered_unique<…> > >
HashCheckDup
multi_index_container<S, indexed_by< sequenced<>, hashed_unique<…> > >
第一个容器list就不用多说了,后两个容器的意义需要你熟悉multi_index_container才可以弄明白。简单地说,就是提供一个容器,该容器的缺省表现类似于list(这是声明中的sequenced<> 所代表的意思),但同时该容器还拥有一个唯一索引(即ordered_unique<…> 和 hashed_unique<…> 所代表的意思),该索引确保了重复元素不会被插入到容器中。
ordered_unique<…> 实现了类似于set容器的接口,它使用operator<来判断元素是否重复,所以要求我们的问题状态类S必须提供operator<操作符。相应地,hashed_unique<…>实现了类似于hash_set容器的接口,它使用operator==来判断元素是否重复并使用hash函数计算状态结点对象的hash值,所以要求我们的问题状态类S必须提供operator==操作符和对应的hash函数。这点与我们在旧版本的DFS/BFS中对问题状态的要求是一样的。
在引入了multi_index_container容器后,我们就不需要显式地调用某个checkDup()函数对象了。在对multi_index_container容器指定了唯一索引后,如果我们把一个重复的元素插入该容器,将会被拒绝且容器中的元素不会发生任何变化,这样就达到了我们要进行状态查重的目的。因此,我们在DFS/BFS的搜索算法代码中可以删掉对checkDup()函数对象进行定义和调用的代码。
以上就是对DFS/BFS代码的主要修改。除些以外,我还把原来在BFS_DFS_v1.6版本中使用bool类型来指定选择BFS还是DFS的方法,改为使用名为BFS和DFS的类型来进行选择。我觉得这样的用户代码具有更好的可读性,如下:
    StateSpaceTreeSearch<SokoState, BFS, OrderCheckDup> sokoSearch;
    int n = sokoSearch(initState, printAnswer);
 
以下就是完整的BFS_DFS_v2版本的代码。

// Filename: BFS_DFS_v2.hpp
#ifndef _BFS_DFS_HPP
#define _BFS_DFS_HPP

#include <list>
#include <vector>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/hashed_index.hpp>

using std::list;
using std::vector;
using boost::multi_index::multi_index_container;
using boost::multi_index::indexed_by;
using boost::multi_index::sequenced;
using boost::multi_index::ordered_unique;
using boost::multi_index::hashed_unique;
using boost::multi_index::identity;

struct NoCheckDup {};
struct OrderCheckDup {};
struct HashCheckDup {};

struct BFS {};
struct DFS {};

// 状态空间树搜索模板
// State:问题状态类,提供nextStep()和isTarget()成员函数
// SearchType:可选择BFS或DFS,缺省为BFS
// CheckDup:状态查重算法,可选择NoCheckDup,HashCheckDup,OrderCheckDup
template < class State, class SearchType = BFS, class CheckDup = NoCheckDup >
class StateSpaceTreeSearch
{
    // 当SearchType不为BFS和DFS时,出错
    template <class Cont, class S>
    struct SearchInserter
    {
    };

    // BFS算法对应的新结点插入策略
    template <class Cont>
    struct SearchInserter<Cont, BFS>
    {
    public:
        SearchInserter(Cont& c) : c_(c) {}
        typedef typename Cont::iterator iterator;
        typedef typename Cont::value_type value_type;
        void operator()(iterator it, const value_type& v) {
            c_.push_back(v);  //新结点插入到列表的末端,即未搜索的结点后
        }
    private:
        Cont& c_;
    };

    // DFS算法对应的新结点插入策略
    template <class Cont>
    struct SearchInserter<Cont, DFS>
    {
    public:
        SearchInserter(Cont& c) : c_(c) {}
        typedef typename Cont::iterator iterator;
        typedef typename Cont::value_type value_type;
        void operator()(iterator it, const value_type& v) {
            c_.insert(++it, v);  //新结点插入到未搜索的结点前
        }
    private:
        Cont& c_;
    };

    // 当CheckDup不为下面偏特化的三者之一时,出错
    template <class S, class C>
    struct Container
    {
    };

    // NoCheckDup策略采用list为容器
    template <class S>
    struct Container<S, NoCheckDup>
    {
        typedef list<S> Cont;
    };

    // OrderCheckDup策略采用multi_index_container为容器,有一个order唯一索引
    template <class S>
    struct Container<S, OrderCheckDup>
    {
        typedef multi_index_container< S,
            indexed_by< sequenced<>, ordered_unique< identity<S> > >
        > Cont;
    };

    // HashCheckDup策略采用multi_index_container为容器,有一个hash唯一索引
    template <class S>
    struct Container<S, HashCheckDup>
    {
        struct HashFcn
        {
            size_t operator()(const S& s) const { return s.hash(); }
        };
        typedef multi_index_container< S,
            indexed_by< sequenced<>, hashed_unique< identity<S>, HashFcn > >
        > Cont;
    };

public:
    typedef typename Container<State, CheckDup>::Cont Cont;
    typedef typename Cont::iterator iterator;
    typedef SearchInserter<Cont, SearchType> Inserter;

    template <class Func>
    int operator()(const State& initState, Func afterFindSolution) const
    // initState : 初始化状态,类State应提供成员函数nextStep()和isTarget(),
    //             nextStep()用vector<State>返回下一步可能的所有状态,
    //             isTarget()用于判断当前状态是否符合要求的答案;
    // afterFindSolution : 仿函式,在找到一个有效答案后调用之,它接受一个
    //                     const State&,并返回一个bool值,true表示停止搜索,
    //                     false表示继续搜索
    // return : 找到的答案数量
    {
        Cont states;
        Inserter inserter(states);
        states.push_back(initState);
        iterator head = states.begin();  //指向下个搜索的结点
        vector<State> nextStates;
        int n = 0;  //记录找到的解答数量
        bool stop = false;
        while (!stop && head != states.end())
        {
            State s = *head;  //搜索一个结点
            nextStates.clear();
            s.nextStep(nextStates);  //从搜索点生成下一层结点
            for (typename vector<State>::iterator i = nextStates.begin();
                 i != nextStates.end(); ++i)
            {
                if (i->isTarget())
                {  // 找到一个目标状态
                    ++n;
                    if (stop = afterFindSolution(*i))  //处理结果并决定是否停止
                    {
                        break;
                    }
                } else {  // 不是目标状态,插入树中,树所使用的容器类会自动查重
                    inserter(head, *i);
                }
            }
            ++head;  //指针移到下一个元素
        }
        return n;
    }
};

#endif


 

 相对应的新的推箱子程序如下:

// soko.cpp -- 推箱子主程序

#include <iostream>
#include "BFS_DFS_v2.hpp"
#include "SokoState_v1.h"

using namespace std;

bool printAnswer(const SokoState& s)
{
    s.printAnswer(cout);
    cout << endl;
    return true;
}

int main()
{
    SokoState initState;
    cin >> initState;
    cout << initState;
    StateSpaceTreeSearch<SokoState, BFS, OrderCheckDup> sokoSearch;
    int n = sokoSearch(initState, printAnswer);
    if (n == 0)
    {
        cout << "No answer." << endl;
    }
    return 0;
}


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐