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