每日一題23:查找樹基本操作

距離上一篇博客已經過去了四天,“每日一題”這個標題給的壓力還是挺大的,這四天一直在學習查找樹,同時更進一步地接觸STL,所以更新慢了。查找樹的刪除操作整整花了一天才學會,比較耗時,另外兩個有點麻煩的地方是迭代器的前進與後退,需要畫圖再根據查找樹的性質推理。這三個操作算得上兩個題吧,整個查找樹操作算得上一個題,這樣想想,嗯,勉強對得起標題啦,O(∩_∩)O。好了,閒話少說,上代碼,吸取了以前的教訓,註釋全部寫在源代碼中,直接複製過來就可以了,關於註釋只寫了比較麻煩的一些操作的,那些簡單的就沒寫了,希望以後拿來看的時候還能看懂:

#ifndef _SEARCH_TREE_H_
#define _SEARCH_TREE_H_

#include "../Utilites/type_traits.h"
#include "Functor.h"

namespace MyDataStructure
{
    //將node結構分爲兩層,可以帶來一定的彈性,節點
    //基類只負責節點連接的功能,而子類增加了實際的
    //數據成員,所謂的彈性在這裏得到了體現,本例的
    //子類只需要擁有一個數據成員,而對於紅黑樹而言,
    //子類還需要表示顏色的成員,所有使用分層結構可以
    //複用
    struct BinaryNodeBase
    {
        typedef BinaryNodeBase* BasePtr;
        BasePtr parent; //爲了支持迭代器,增設該指針
        BasePtr left;
        BasePtr right;

        //查找以本節點爲根的樹中的最小值,
        //如果有左子樹,則一直左轉到最左邊
        //的節點,否則本身爲最小
        BasePtr minimum()
        {
            BinaryNodeBase* res = this;
            while (res->left)
            {
                res = res->left;
            }
            return res;
        }

        //查找以本節點爲根的樹中的最大值,
        //如果有右子樹,則一直右轉到最右邊
        //的節點,否則本身爲最大
        BasePtr maximum()
        {
            BinaryNodeBase* res = this;
            while (res->right)
            {
                res = res->right;
            }
            return res;
        }
    };

    //具體的節點類
    template<typename ValueType>
    struct BinaryNode : public BinaryNodeBase
    {
        typedef BinaryNode<ValueType>* LinkType;
        ValueType value;
    };


    //查找樹迭代器基類
    struct  SearchTreeIteratorBase
    {
        typedef BinaryNodeBase::BasePtr BasePtr;
        BasePtr node;

        //讓迭代器移動到下一個節點
        void Increament()
        {
            //如果迭代器指示的節點有右子節點,
            //則以該右子節點爲根的樹的最小值節
            //點就是下一個迭代器該指示的節點
            if (node->right) node = node->right->minimum();
            else
            {
                //否則,如果當前指示的節點是右子節
                //點,就需要往上搜索第一個不是右子節點
                //的節點,如果當前指示的節點是左子節點,
                //那麼父節點就是了。畫圖根據查找樹的性質容易
                //的到算法
                BasePtr p = node->parent;
                //根節點的父節點爲nullptr,需要注意
                while (p && p->right == node)
                {
                    node = p;
                    p = p->parent;
                }
                node = p;
            }
        }

        //讓迭代器移動到上一個節點,和Increament
        //算法大同小異,對稱地考慮
        void Decreament()
        {
            if (node->left) node = node->left->maximum();
            else
            {
                BasePtr p = node->parent;
                while (p && p->left == node)
                {
                    node = p;
                    p = p->parent;
                }
                node = p;
            }
        }

        //模擬指針行爲,迭代器是一種智能指針
        bool operator ==(const SearchTreeIteratorBase& it)
        {
            return this->node == it.node;
        }

        bool operator != (const SearchTreeIteratorBase& it)
        {
            return this->node != it.node;
        }
    };

    //真正的查找樹迭代器
    template<typename Value,typename Ref,typename Ptr>
    struct SearchTreeIterator : public SearchTreeIteratorBase
    {
        typedef Value ValueType;
        typedef Ref reference;
        typedef Ptr pointer;
        typedef SearchTreeIterator<Value, Value&, Value*> iterator;
        typedef SearchTreeIterator<Value, const Value&, Value*> const_iterator;
        typedef SearchTreeIterator<Value, Value&, Value*> self;
        typedef BinaryNodeBase* LinkType;
        typedef BinaryNode<Value>* NodePtr;

        SearchTreeIterator() {}
        SearchTreeIterator(LinkType x) { node = x; }
        SearchTreeIterator(const iterator& it) { node = it.node; }
        self& operator =(const iterator& it) { node = it.node; return *this; }

        //模擬指針行爲,迭代器是一種智能指針
        reference operator *(){ return NodePtr(node)->value; }
        pointer operator ->() { return &(operator*()); }

        self& operator ++()
        {
            Increament();
            return *this;
        }

        self operator ++(int)
        {
            self tmp = *this;
            Increament();
            return tmp;
        }

        self& operator --()
        {
            Decreament();
            return *this;
        }

        self operator --(int)
        {
            self tmp = *this;
            Decreament();
            return tmp;
        }
    };

    template < typename T, typename Compare = less<T>>
    class SearchTree
    {
    public:
        typedef typename ParameterTrait<T>::ParameterType ParameterType;
        typedef typename BinaryNode<T> NodeType;
        typedef typename BinaryNode<T>* NodePtr;
        typedef typename BinaryNode<T>::BasePtr BasePtr;
        typedef typename SearchTreeIterator<T, T&, T*>::iterator iterator;
    public:

        //有兩種方式構造一棵查找樹:
        //  1)構造一顆空樹。
        //  2)複製一顆已經存在的樹
        SearchTree() : size(0),head(nullptr){}
        SearchTree(const SearchTree& st)
        {
            copy(st);
        }
        SearchTree& operator = (const SearchTree& st)
        {
            if (&st != this)
            {
                Clear();
                copy(st);
            }
            return *this;
        }
        ~SearchTree(){ Clear(); }
        const int Size(){ return size; }
        void Clear()
        {
            _destroy(head);
            head = nullptr;
            size = 0;
        }

        //允許插入重複值的方法(批量版)
        void Insert(T values[], int count)
        {
            for (int i = 0; i < count; ++i)
            {
                Insert(values[i]);
            }
        }

        //允許插入重複值的方法
        void Insert(const ParameterType value)
        {
            //q表示插入後新節點的父節點
            BasePtr q = find_position_for_insert(value);
            //q爲nullptr,樹是空的,所以構造頭結點
            if (q == nullptr)
            {
                create_head(value);
            }
            //否則,插入就好了
            else
            {
                NodePtr node = create_node(value);
                insert(&q, node);
            }
        }

        //不允許插入重複值的方法(批量版)
        void UniqueInsert(T values[], int count)
        {
            for (int i = 0; i < count; ++i)
            {
                UniqueInsert(values[i]);
            }
        }
        //不允許插入重複值的方法
        void UniqueInsert(const ParameterType value)
        {
            //q同Insert中,通過p可以知道樹中是否
            //已經插入同樣的值了
            BasePtr q = nullptr;
            BasePtr p = find_first(head,value, &q);
            if (q == nullptr)
            {
                create_head(value);
            }
            else
            {
                //p不爲空,插入會變成重複值,所以什麼也不做了
                if (p != nullptr) return;
                NodePtr node = create_node(value);
                insert(&q, node);
            }
        }

        iterator FindFirst(const ParameterType value)
        {
            BasePtr temp = nullptr;
            return iterator(find_first(head,value,&temp));
        }

        iterator FindLast(const ParameterType value)
        {
            BasePtr temp = nullptr;
            return iterator(find_last(head, value, &temp));
        }

        //從樹中刪除一個節點
        void Erase(iterator& it)
        {
            erase(it.node);
        }

        //從樹中刪除一個節點
        void Erase(const ParameterType value)
        {
            Erase(FindFirst(value));
        }

        //查找樹中的元素是排序的,獲得樹中最左端的節點
        iterator First()
        {
            BasePtr first = nullptr;
            if (head) first = head->minimum();
            return iterator(first);
        }
        //查找樹中的元素是排序的,獲得樹中最右端的節點
        iterator Last()
        {
            BasePtr last = nullptr;
            if (head) last = head->maximum();
            return iterator(last);
        }

        //nullptr指示到了盡頭了
        iterator End()
        {
            return iterator(nullptr);
        }

    private:
        NodePtr create_node(const ParameterType value)
        {
            NodePtr node = new NodeType;
            node->parent = node->left = node->right = nullptr;
            node->value = value;
            return node;
        }
        void copy(const SearchTree& st)
        {
            __copy(&head, nullptr, st.head);
        }

        //複製只能採取先序的方式進行,要讓子
        //節點有歸屬,得先構造父節點
        void __copy(BasePtr* dst,BasePtr p, BasePtr src)
        {
            if (src == nullptr)
            {
                *dst = nullptr;
                return;
            }
            *dst = create_node(((NodePtr)src)->value);
            (*dst)->parent = p;
            ++size;
            __copy(&((*dst)->left),*dst, src->left);
            __copy(&((*dst)->right),*dst,src->right);
        }

        //摧毀最好採取後序的方式進行,
        //這樣思考起來會自然更多
        void _destroy(BasePtr p)
        {
            if (p == nullptr) return;
            _destroy(p->left);
            _destroy(p->right);
            delete (NodePtr)p;
        }

        //刪除一個節點是最麻煩的操作
        void erase(BasePtr node)
        {
            if (node == nullptr) return;


            //如果待刪除的節點只有一個子節點
            //,直接讓節點的父節點繞過它,指向
            //它的子節點就好了,代碼之所以複雜,
            //是因爲需要考慮父節點是否爲空,以及
            //處理節點的連接時要考慮parent指針,
            //而維護parent指針則是爲了支持迭代器


            //只有右節點
            if (node->left == nullptr)
            {
                BasePtr p = node;
                node = node->right;
                BasePtr q = parent(p);
                if(node != nullptr) node->parent = q;
                if (p == head) head = node;
                else
                {
                    if (q->left == p) q->left = node;
                    else q->right = node;
                }

                delete NodePtr(p);
            }
            //只有右節點
            else if (node->right == nullptr)
            {
                BasePtr p = node;
                node = node->left;
                BasePtr q = parent(p);
                if (node != nullptr) node->parent = q;
                if (p == head) head = node;
                else
                {
                    if (q->left == p) q->left = node;
                    else q->right = node;
                }
                delete NodePtr(p);
            }
            //上面兩種情況還包含了沒有子節點的情況

            //待刪除節點具有兩個節點,根據查找二叉樹的性質,
            //使用其左子樹中的最大值代替待刪除節點,左子樹最大值
            //節點要麼某個節點的右子節點,要麼就是待刪除節點的
            //左子節點
            else
            {
                BasePtr p = (node->left)->maximum(); 
                BasePtr q = p->parent;     //因爲node有兩個子節點,所以p的parent不會爲空

                //把待刪除節點的值替換爲其左子樹中的
                //最大值,實際上直接將節點進行替換更合理,
                //在節點值爲類並擁有許多筆數據時效率更高,
                //只是指針操作跟複雜一點
                ((NodePtr)node)->value = ((NodePtr)p)->value;

                //最大值是某個節點的右子節點,
                //該最小值節點最多擁有一個左子樹
                //否則子樹中就有一個值比它還大,
                //於是調整最大值節點的左子樹
                if (q != node)
                { 
                    q->right = p->left;     
                }
                //最小值是左子節點,此時左子樹最多有一個
                //左子樹,於是調整最大值節點的左子樹
                else 
                { 
                    q->left = p->left;
                }
                //連接時處理parent指針
                if (p->left != nullptr) p->left->parent = q;
                delete NodePtr(p);
            }
            --size;
        }
        void insert(BasePtr* parent, BasePtr node)
        {
            node->parent = *parent;
            if (*parent == nullptr)
                head = node;
            else if (compare((NodePtr(node))->value, ((NodePtr)(*parent))->value))
                (*parent)->left = node;
            else
                (*parent)->right = node;
            ++size;
        }

        void create_head(const ParameterType value)
        {
            head = create_node(value);
            ++size;
        }
        BasePtr left(const BasePtr node){ if (node) return node->left; else return nullptr; }
        BasePtr right(const BasePtr node){ if (node) return node->right; else return nullptr; }
        BasePtr parent(const BasePtr node){ if (node) return node->parent; else return nullptr; }
        BasePtr find_first(BasePtr h,const ParameterType value,BasePtr* last_visited_valid_node)
        {
            BasePtr res = nullptr;
            BasePtr p = h;
            *last_visited_valid_node = nullptr;
            while (p)
            {
                *last_visited_valid_node = p;
                if (compare(value, ((NodePtr)p)->value)) p = p->left;
                else if (value == ((NodePtr)p)->value){ res = p; break; }
                else p = p->right;
            }
            return res;
        }
        //假設有重複,那麼根據插入算法,後來的重複值都被插到第一個值
        //的右子樹中,但未必就是它的右子節點,所以查找到一個節點後,
        //還要在該節點的右子樹中繼續查找
        BasePtr find_last(BasePtr h,const ParameterType value, BasePtr* last_visited_valid_node)
        {
            BasePtr res = find_first(h,value, last_visited_valid_node);
            if (res != nullptr)
            {
                BasePtr last_visited_valid_node_1 = nullptr;
                BasePtr res_1 = find_first(res->right, value, &last_visited_valid_node_1);
                if (res_1 != nullptr)
                {
                    res = res_1;
                    *last_visited_valid_node = last_visited_valid_node_1;
                }
            }
            return res;
        }
        //找到位置一定是插入節點的父節點
        BasePtr find_position_for_insert(const ParameterType value)
        {
            BasePtr res = nullptr;
            BasePtr p = head;
            while (p)
            {
                res = p;
                if (compare(value, ((NodePtr)p)->value)) p = p->left;
                else p = p->right;
            }
            return res;
        }

    private:
        BasePtr head;
        int size;
        Compare compare;
    };
}

#endif

上面連同註釋一共使用了500行代碼,應該還有可以精簡的地方,代碼行數越少越好,想起剛學編程的時候只想着怎樣把代碼寫長,不禁好笑,現在呢覺得對於完成某個功能的代碼,不丟失可讀性的情況下越短越好,個人寫過的總代碼行呢,則是越多越好,這一多一少應該就是作爲一個學習者應該追求的吧。

Functor.h文件的代碼:

#ifndef _FUNCTOR_H_
#define _FUNCTOR_H_
#include "../Utilites/type_traits.h"
namespace MyDataStructure
{

    template<typename T>
    struct less
    {
        typedef typename ParameterTrait<T>::ParameterType ParameterType;
        bool operator ()(const ParameterType op1, const ParameterType op2)
        {
            return op1 < op2;
        }
    };
}

#endif

type_traits.h文件前面的博文已經展示過,不在列出。測試代碼(有重複插入):

// SearchTreeTest.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include <iostream>
#include "../../include/SearchTree.h"


using namespace MyDataStructure;
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
    int v[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    SearchTree<int> st;
    st.Insert(v, 10);
    for (SearchTree<int>::iterator it = st.Last(); it != st.End(); --it)
        cout << *it << " ";
    cout << endl;
    SearchTree<int> st1(st);
    st = st;
    SearchTree<int>::iterator it1 = st.FindFirst(9), it2 = st.FindLast(9);
    cout << "they are " << (it1 == it2) <<" : "<<*it1<<" "<<*it2<<endl;
    for (int i = 0; i < 10; ++i)
    {
        st.Erase(v[i]);
    }
    for (SearchTree<int>::iterator it = st.Last(); it != st.End(); --it)
        cout << *it << " ";
    cout << endl;

    for (SearchTree<int>::iterator it = st1.First(); it != st1.End(); ++it)
        cout << *it << " ";
    cout << endl;

    const int n = st.Size();
    cout << n << endl;
    const int m = st1.Size();
    cout << m << endl;
    st.Clear();
    return 0;
}

運行結果:
這裏寫圖片描述
把插入操作改爲不允許插入重複值的版本:

st.Insert(v, 10);

改爲:

st.UniqueInsert(v, 10);

在運行,得到結果:
這裏寫圖片描述
可以看到,程序通過了基本的測試,而對於一些比較複雜的測試則不再構造了,本序列博客與程序只爲學習算法和數據結構,程序的健壯性還暫不考慮。寫這個數據結構最複雜的就是刪除操作,參看了四本書:《STL源碼剖析》、《算法導論》、《數據結構與算法分析(第二版)》以及嚴蔚敏版的《數據結構》,個人感覺最後一本講得最清楚,倒數第二本是用遞歸實現的,容易懂,但是不是太喜歡。寫完了這個結構就可以做許多關於二叉樹的題目了,如果不以查找樹這種方式構建二叉樹,輸入就太麻煩了,現在有了這一個類,後面的題目全部自主產權,還能繼續測試這個類,真是一舉兩得。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章