接下來是第六章的一道習題,要求實現一個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演算法。