zoukankan      html  css  js  c++  java
  • 数据结构基础(17) --二叉查找树的设计与实现

    二叉排序树的特征

    二叉排序树或者是一棵空树,或者是具有如下特性的二叉树:

        1.每一元素都有一个键值, 而且不允许重复;

        2.若它的左子树不空,则左子树上所有结点的值均小于根结点的值;

        3.若它的右子树不空,则右子树上所有结点的值均大于根结点的值;

        4.它的左、右子树也都分别是二叉排序树。



    二叉排序树保存的元素构造

    template <typename Type>
    class Element
    {
    public:
        Element(const Type& _key): key(_key) {}
        Element():key(0) {}
        Type key;
        //在这儿可以很容易的添加更多的数据
        //方便对Element进行扩展
    };

    二叉排序树节点的设计与实现

    template <typename Type>
    class BstNode
    {
        friend class BsTree<Type>;
    
    public:
        BstNode(const Element<Type> &_data = 0,
                BstNode *_leftChild = NULL,
                BstNode *_rightChild = NULL)
            : data(_data), leftChild(_leftChild), rightChild(_rightChild) {}
    
        const Type &getData() const
        {
            return data.key;
        }
    
    private:
        //Node当中保存的是Element元素
        Element<Type> data;
        BstNode *leftChild;
        BstNode *rightChild;
    
        void display(int i);
    };
    //中序遍历二叉树:
    //能够保证该二叉树元素按照递增顺序打印出来
    template <typename Type>
    void BstNode<Type>::display(int i)
    {
        //首先访问左子树
        if (leftChild != NULL)
            leftChild->display(2*i);
    
        //访问中间节点
        //Number表示为如果该树为完全二叉树/满二叉树, 其编号为几
        std::cout << "Number: " << i << ", data.key = " << data.key << std::endl;
    
        //访问右子树
        if (rightChild != NULL)
            rightChild->display(2*i+1);
    }

    二叉排序树的构造

    template <typename Type>
    class BsTree
    {
    public:
    //构造与析构
        BsTree(BstNode<Type> *init = NULL): root(init) {}
        ~BsTree()
        {
            if (!isEmpty())
                makeEmpty(root);
        }
    
    //二叉查找树的三大主力:插入, 删除, 搜索(又加入了一个迭代搜索)
        //插入
        bool insert(const Element<Type> &item);
        //删除
        void remove(const Element<Type> &item)
        {
            remove(item, root);
        }
        //递归搜索
        const BstNode<Type>* search(const Element<Type> &item)
        {
            return search(item, root);
        }
        //迭代搜索
        const BstNode<Type> *searchByIter(const Element<Type> &item);
    
    //实用函数
        void display() const
        {
            if (root != NULL)
                root->display(1);
        }
        void visit(BstNode<Type> * currentNode) const
        {
            std::cout << "data.key = "
                      << currentNode->data.key << std::endl;
        }
        bool isEmpty() const
        {
            return root == NULL;
        }
        void makeEmpty(BstNode<Type> *subTree);
        //中序遍历
        void levelOrder() const;
    
    private:
        const BstNode<Type>* search(const Element<Type> &item,
                                    const BstNode<Type> *currentNode);
        void remove(const Element<Type> &item,
                    BstNode<Type> *¤tNode);
    
    private:
        BstNode<Type> *root;
    };

    二叉排序树的插入算法

        根据动态查找表的定义,插入操作在查找不成功时才进行;若二叉排序树为空树,则新插入的结点为新的根结点;否则,新插入的结点必为一个新的叶子结点,其插入位置由查找过程得到。

    //二叉排序树插入的实现与解析
    template <typename Type>
    bool BsTree<Type>::insert(const Element<Type> &item)
    {
        //如果这是新插入的第一个节点
        if (root == NULL)
        {
            root = new BstNode<Type>(item);
            root->leftChild = root->rightChild = NULL;
            return true;
        }
    
        BstNode<Type> *parentNode = NULL;   //需要插入位置的父节点
        BstNode<Type> *currentNode = root;  //需要插入的位置
        while (currentNode != NULL)
        {
            //如果二叉树中已经含有了该元素, 则返回插入出错
            if (item.key == currentNode->data.key)
                return false;
    
            parentNode = currentNode;
            //如果要插入的元素大于当前指向的元素
            if (item.key < currentNode->data.key)
                currentNode = currentNode->leftChild;   //向左搜索
            else
                currentNode = currentNode->rightChild;  //向右搜索
        }
    
        //此时已经查找到了一个比较合适的插入位置了
        if (item.key < parentNode->data.key)
            parentNode->leftChild = new BstNode<Type>(item);
        else
            parentNode->rightChild = new BstNode<Type>(item);
    
        return true;
    }

    二叉排序树的查找算法

    若二叉排序树为空,则查找不成功;否则:

        1.若给定值等于根结点的关键字,则查找成功;

        2.若给定值小于根结点的关键字,则继续在左子树上进行查找;

        3.若给定值大于根结点的关键字,则继续在右子树上进行查找。

    //二叉排序树搜索的设计与实现
    //递归搜索
    template <typename Type>
    const BstNode<Type>* BsTree<Type>::search(const Element<Type> &item,
            const BstNode<Type> *currentNode)
    {
        if (currentNode == NULL)
            return NULL;
        if (currentNode->data.key == item.key)
            return currentNode;
    
        if (item.key < currentNode->data.key)
            return search(item, currentNode->leftChild);
        else
            return search(item, currentNode->rightChild);
    }
    //迭代搜索
    template <typename Type>
    const BstNode<Type> *BsTree<Type>::searchByIter(const Element<Type> &item)
    {
        for (BstNode<Type> *searchNode = root;
                searchNode != NULL;
                /*empty*/)
        {
            if (item.key == searchNode->data.key)
                return searchNode;
    
            if (item.key < searchNode->data.key)
                searchNode = searchNode->leftChild;
            else
                searchNode = searchNode->rightChild;
        }
    
        return NULL;
    }

    二叉排序树的删除算法

        和插入相反,删除在查找成功之后进行,并且要求在删除二叉排序树上某个结点之后,仍然保持二叉排序树的特性

     

    删除分三种情况:

        1.被删除的结点是叶子节点:其双亲结点中相应指针域的值改为“空”, 并将该节点删除;

        2.被删除的结点只有左子树或者只有右子树:其双亲结点的相应指针域的值改为 “指向被删除结点的左子树或右子树”, 然后删除该节点;

        3.被删除的结点既有左子树,也有右子树:以其前驱替代之,然后再删除该前驱结点;

    //二叉排序树节点删除的实现与解析如下
    template <typename Type>
    void BsTree<Type>::remove(const Element<Type> &item,
                              BstNode<Type> *¤tNode)
    {
        if (currentNode != NULL)
        {
            //如果要删除的元素小于当前元素
            if (item.key < currentNode->data.key)
                remove(item, currentNode->leftChild);   //向左搜索删除
            //如果要删除的元素大于当前元素
            else if (item.key > currentNode->data.key)
                remove(item, currentNode->rightChild);  //向右搜索删除
            //如果要删除掉的元素等于当前元素(找到要删除的元素了)
            // 并且当前节点的左右子女节点都不为空
            else if ((currentNode->leftChild != NULL) && (currentNode->rightChild != NULL))
            {
                //从当前节点的右子女节点开始,
                //不断向左寻找, 找到从当前节点开始中序遍历的第一个节点
                //找到的这一个节点是在当前子树中, 大于要删除的节点的第一个节点
                BstNode<Type> *tmp = currentNode->rightChild;
                while (tmp->leftChild != NULL)
                    tmp = tmp->leftChild;
    
                //用搜索到的节点值覆盖要删除的节点值
                currentNode->data.key = tmp->data.key;
                //删除搜索到的节点
                remove(currentNode->data, currentNode->rightChild);
            }
            //如果当前节点就是要删除的节点
            //并且其左子女(和/或)右子女为空
            //默认包含了左右子女同时为空的情况:
            //即: 在if中肯定为true
            else
            {
                BstNode<Type> *tmp = currentNode;
                //如果左子女为空
                if (currentNode->leftChild == NULL)
                    //则用他的右子女节点顶替他的位置
                    currentNode = currentNode->rightChild;
                //如果右子女为空
                else
                    //则用他的左子女节点顶替他的位置
                    currentNode = currentNode->leftChild;
                //释放节点
                delete tmp;
            }
        }
    }

    二叉查找树的几个实用操作

    //清空二叉树
    template <typename Type>
    void BsTree<Type>::makeEmpty(BstNode<Type> *subTree)
    {
        if (subTree != NULL)
        {
            if (subTree->leftChild != NULL)
                makeEmpty(subTree->leftChild);
            if (subTree->rightChild != NULL)
                makeEmpty(subTree->rightChild);
    
            delete subTree;
        }
    }
    //二叉查找树的层次遍历
    template <typename Type>
    void BsTree<Type>::levelOrder() const
    {
        std::queue< BstNode<Type> * > queue;
        queue.push(root);
    
        while (!queue.empty())
        {
            BstNode<Type> *currentNode = queue.front();
            queue.pop();
    
            visit(currentNode);
            if (currentNode->leftChild != NULL)
                queue.push(currentNode->leftChild);
            if (currentNode->rightChild != NULL)
                queue.push(currentNode->rightChild);
        }
    }

    二叉排序树的性能分析

         对于每一棵特定的二叉排序树,均可按照平均查找长度的定义来求它的 ASL 值,显然,由值相同的 n 个关键字,构造所得的不同形态的各棵二叉排序树的平均查找长度的值不同,甚至可能差别很大(如果二叉查找树退化成一条链表, 则其插入/删除/查找的性能都会退化为O(N))。

         但是在随机情况下, 二叉排序树的搜索, 插入, 删除操作的平均时间代价为O(logN);

  • 相关阅读:
    supervisord + docker run = web页面管理运行的docker
    docker之Dockerfile实践用dockerfile构建nginx环境
    Dockerfile文件详解
    【docker】CMD ENTRYPOINT 区别 终极解读!
    golang html/template
    网络连接带宽的理论最大值
    Queue length 和 Queue depth 的区别
    .tar.gz 文件和 .tar.xz 文件的区别
    如何同时在Isilon的所有网卡上抓取网络包?
    Reimage Isilon cluster,结果忘记了修改管理口的netmask,怎么办?
  • 原文地址:https://www.cnblogs.com/itrena/p/5926989.html
Copyright © 2011-2022 走看看