接下来是第六章的一道习题,要求实现一个
binary_tree_inserter,以使用mpl::copy算法从其它序列生成一棵二分查找树(Binary Search Tree,即满足以下条件的一种二分树:左子树所有元素小于根,右子树所有元素大于根,且左右子树全都是二分查找树)。习题中给出的测试代码如下:
typedef mpl::copy< mpl::vector_c<int,17,25,10,2,11> , binary_tree_inserter< tree<> > >::type bst; // int_<17> // / / // int_<10> int_<25> // / / // int_<2> int_<11> BOOST_STATIC_ASSERT(( mpl::equal< inorder_view<bst> , mpl::vector_c<int,2,10,11,17,25> >::value )); |
使用
mpl::copy算法依据一个mpl::vector<>生成二分查找树,生成过程中使用一个inserter依次将mpl::vector<>中的各个元素插入到开始为空的tree<>中,inserter要确保插入后,tree<>保持满足二分查找树的条件。
明显,
inserter要做的就是:从当前tree<>的根开始,如果要插入的元素小于根,则选择左子树插入,否则选择右子树插入;左右子树的插入方法与上同,重复以上步骤。
参照书中给出的关于
inserter的例子,
binary_tree_inserter
应该是这样的:
template <class S, class T> struct push_bst; template <class S> struct binary_tree_inserter : mpl::inserter< S, push_bst<_, _> > { }; |
其中有一个
push_bst是辅助类,它执行实际的插入算法。
binary_tree_inserter
基于push_bst实现。留意binary_tree_inserter定义中的push_bst<_, _>,使用了mpl的占位符(placeholder),占位符的确是一个非常有意思,也非常有用的工具。它可以很方便地将mpl::copy算法在调用binary_tree_inserter时传入的参数,转送到push_bst,不需要你显式地定义出来,大大简化了我们编写MPL程序的工作。
接着看看
push_bst的实现。push_pst的实现分为三种情况:插入到空的树中、插入到只有一个元素的树中、插入非平凡的树中。为了表示空的树,还要定义一个none类来表示空的元素。根据这些想法,修改tree的定义如下:
struct none {}; template <class R = none, class LC = none, class RC = none> struct tree { typedef tree type; typedef R root; typedef LC left; typedef RC right; }; |
空的树既可以直接用
none表示,也可以表示为tree<none, none, none>,所以push_bst的第一种情况可以写为:
template <class T> struct push_bst<none, T> : T { }; template <class T> struct push_bst<tree<>, T> : tree<T> { }; |
push_bst的第三种情况(插入到一棵真正的
tree中)写为:
template <class R, class LC, class RC, class T> struct push_bst<tree<R, LC, RC>, T> : mpl::eval_if< typename mpl::less<T, R>::type, tree<R, typename push_bst<LC, T>::type, RC>, tree<R, LC, typename push_bst<RC, T>::type> > { }; |
先判断插入的元素
T是否小于被插入树的根,是则将T插入到左子树后形成新的树,否则将T插入右子树后形成新的树。
最后是
push_bst的第二种情况(插入到只有一个元素的树中,且该树使用该元素直接表示),这也是push_bst的主模板,代码如下:
template <class S, class T> struct push_bst : mpl::eval_if< typename mpl::less<T, S>::type, tree< S, T >, tree< S, none, T > > { }; |
代码与前一种情况相似,只不过这次不是用
tree<>来表示树,而是直接以元素本身表示。
其实,第二、三种情况也可以合并起来写,不过就需要另外实现几个辅助类,分别用于取出二分树的根、左子树和右子树。如果给出的是普通的树,取出这几样东西都很容易(因为我们已经在
tree<>的定义里typedef了这几样东西);但是如果给出的是退化的表示方法(即以元素本身来表示只含单个元素的树)的话,就必须使用模板偏特化来实现了:
template <class T> struct root { typedef T type; }; template <class R, class LC, class RC> struct root< tree<R, LC, RC> > { typedef R type; }; template <class T> struct left_child { typedef none type; }; template <class R, class LC, class RC> struct left_child< tree<R, LC, RC> > { typedef LC type; }; template <class T> struct right_child { typedef none type; }; template <class R, class LC, class RC> struct right_child< tree<R, LC, RC> > { typedef RC type; }; |
有了以上辅助类,就可以将
push_bst的后两种情况合并为一种写法:
template <class S, class T> struct push_bst : mpl::eval_if< typename mpl::less<T, typename root<S>::type>::type, tree< typename root<S>::type, typename push_bst<typename left_child<S>::type, T>::type, typename right_child<S>::type >, tree< typename root<S>::type, typename left_child<S>::type, typename push_bst<typename right_child<S>::type, T>::type > > { }; |
当然,另两个
push_bst的特化版本(push_bst<none, T>和push_bst<tree<>, T>)还得保留。不过,比较这两种写法,后一种写法虽然减少了push_bst的特化版本,但是引入了多个辅助类,如果这些辅助类没有其它用途的话,我觉得还是前一种写法更简练些。
在进入到最后的测试代码之前,还要稍微修改一下上一篇给出的
inorder_view,这是因为这次我们加入了一个none,而上一篇给出的inorder_view没有考虑none的情况。改动其实很少,增加一个针对none的特化版本就行了:
template <> struct inorder_view<none> : mpl::vector<> { }; |
照例,最后是测试用的代码:
int main() { typedef mpl::copy< mpl::vector_c<int,17,25,10,2,11> , binary_tree_inserter< tree<> > >::type bst; BOOST_STATIC_ASSERT(( mpl::equal< inorder_view<bst>::type , mpl::vector_c<int,2,10,11,17,25> >::value )); return 0; } |
这次的
inserter实现比上一次的xxxorder_view相比稍微复杂一些,但也不太难,关键是要理解清楚mpl中的inserter的原理,剩下就都好办了。下一篇将会实现一个完整的编译期Binary Tree,包括了一整套的begin、end、iterator、deref、next、prior等等,然后是在此基础上实现的binary_tree_search算法。