接下来是第六章的一道习题,要求实现一个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算法。
编译期BST实现
本文介绍如何使用C++元编程技术实现编译期生成二分查找树(BST)的方法。通过mpl::copy算法与自定义inserter相结合,从指定序列构建出满足BST性质的二叉树,并验证其正确性。
4086

被折叠的 条评论
为什么被折叠?



