C++实现二叉树的插入、删除、查询、遍历

1.二叉树的概念

       树是一些节点的集合,节点之间用边链接,节点之间不能有环路。上层的节点称为父节点,下层节点称为子节点。最上层的节点称为根节点。

      二叉树是特殊的树。对于每个节点而言,与之直接相连的子节点不能超过两个(可以为0)。左边的子节点称为左子树,右边的子节点称为右子树。如下图就是一颗二叉树:

《C++实现二叉树的插入、删除、查询、遍历》

与树相关的一些概念:

         没有任何子节点的节点称为树叶,或叶子节点。

        深度:对于任意节点N,其深度指的是从根节点到N的唯一路径的长。根的深度为0。深度最深的叶子节点的深度为树的深度。可以理解为:树根是一个入口,离树根越远,就越深。如上图:A、B、C的深度为1,D、E的深度为2。

          高:  对于任意节点N,从N到一片树叶的最远路径的长为N的高度。(只可以从从上到下不能经过从下到上的节点。)树叶的高为0。树的高为根的高。如上图,根的高度为2。A的高度为1,其他节点高度为0。


2.二叉树的应用和时间复杂度

         二叉树是一种常见的数据结构,常常用于查找,也运用于unix等常见操作系统的文件系统中。c++STL(标准模板库)中的set和map也使用二叉树中的红黑树实现。

        二叉树的查找思想基于:在二叉树中,对于任意节点N,左子树中的所有项的值不大于节点N中存储的值,左子树中的所有项的值不小于节点N中存储的值。如下图:

《C++实现二叉树的插入、删除、查询、遍历》

这样,在查找时,只需要不断比较需要查找的x与N的大小,若小于N中的值,只需要搜索左子树,若大于N中的值,只需要搜索右子树。这样每次就能缩小搜索的范围。经过证明,普通二叉树的平均时间复杂度是O(LogN)。

       看到这里,我们发现其实二叉树的搜索思想和二分查找一致,每次不断的减少搜索范围。但是二者之间还是有区别的。

       对于二分查找而言,每次的时间复杂度不会超过O(LogN)。但是对于二叉树,搜索时间的复杂度取决于树的形状。在最坏情况下可能达到O(N)。如下图,如果要找到10,则要查找5次。

《C++实现二叉树的插入、删除、查询、遍历》

         那我们为什么还要使用二叉树而不直接使用二分查找来代替?

        这是因为,二分查找一般基于数组,如果需要插入或删除数据,则会带来很大的开销。因为每次插入或者删除数据需要将改变节点之后的数据往后挪或者往前挪。但是对于二叉树而言,只需要改变一下指向下一个节点的指针就可以很方便的实现插入或者删除。而且一些特殊的二叉树如红黑树可以保证查找的最坏复杂度不超过O(LogN)。

       所以,如果是对于静态数据,不需要改变的数据而言,采用数组存储,使用二分查找比较好。而对于动态数据,需要频繁插入或者删除数据的,采取二叉树存储是较好的。


3.c++实现二叉树的插入、查询、遍历、删除(递归实现)


3.1二叉树的插入

       思路:插入数据x,从根节点开始,不断比较节点与x的大小。若x小于节点,下一次比较x与节点的左子树,反之,比较x与节点的右子树。直到遇到一个空的节点,插入数据。(我们不考虑插入重复数据) 。如下图:

《C++实现二叉树的插入、删除、查询、遍历》

过程:比较4与7,4<7,再比较4与7的左子树6,4<6,比较4与6的左子树3,4>3,比较4与3的右子树,为空,插入4。

代码:

template< typename T>
void BinaryTree<T>::insert(const T &theElement, BinaryNode * &t ) {
    if ( nullptr == t ){
        t = new BinaryNode (theElement);
    } else if ( theElement < t->element ) {
          insert( theElement, t->leftNode );
    } else if ( theElement > t->element ) {
          insert ( theElement, t->rightNode );
    } else {//重复的数据不添加到树中
    }
};

3.2二叉树的查找

         思路:与插入类似,不断比较插入值与节点的值。带代码如下:

template< typename T>
bool BinaryTree<T>::isFind(const T &theElement, BinaryNode * t ) const {
    if ( nullptr == t ){
        return false;
    } else if ( theElement < t->element ) {
        return isFind( theElement, t->leftNode );
    } else if ( theElement > t->element ) {
         return isFind ( theElement, t->rightNode );
    } else { //匹配
        return true;
    }
};


3.3二叉树的遍历

      二叉树的遍历有三种方式:

      前序遍历(DLR):首先访问根结点。然后如果有子树,则对于左孩子也采用DLR的遍历规则。没有就忽略。然后如果有右子树,则对右子树也采用DRL的遍历规则。没有就忽略。

      中序遍历(LDR):首先访问根节点的左子树(对左子树也采用LDR),没有则忽略。再访问根节点。最后则对右子树也采用LDR的遍历规则,没有就忽略。

      后序遍历:首先访问根节点的左子树(对左子树也采用LRD),没有则忽略。再对右子树也采用LRD的遍历规则,没有就忽略。最后则对右子树也采用LRD的遍历规则,没有就忽略。

    三种遍历方式其实是根据根节点的访问顺序命名的。根最先方位为前序,次之访问为中序遍历。最后访问为后序遍历。

先序遍历图1的二叉树,结点的访问顺序为:  e→b→a→d→c→f→g

中序遍历图1的二叉树,结点的访问顺序为:abcdefg

后序遍历图1的二叉树,结点的访问顺序为:  acdbgfe

《C++实现二叉树的插入、删除、查询、遍历》

                             图1

    这里采用递归方式实现:

   前序遍历:

template< typename T>
void BinaryTree<T>::preOrder( BinaryNode *bNode ) const {
    if( nullptr != bNode ) {
        std::cout << bNode->element << " " ;
        preOrder(bNode->leftNode);
        preOrder(bNode->rightNode);
    }

};

中序遍历:

template< typename T>
void BinaryTree<T>::inOrder( BinaryNode *bNode ) const {
    if( nullptr != bNode ) {
        inOrder(bNode->leftNode);
        std::cout << bNode->element << " " ;
        inOrder(bNode->rightNode);
    }
};

后序遍历:

template< typename T>
void BinaryTree<T>::postOrder( BinaryNode *bNode ) const {
    postOrder(bNode->leftNode);
    postOrder(bNode->rightNode);
    std::cout << bNode->element << " " ;
};


3.4二叉树的删除

      二叉树的插入需要分三种情况考虑。第一种,删除节点是树叶,则直接删除;第二种是被删除的节点只有一个子节点,此时只需要将删除节点的上一个节点的指向该节点的指针指向该节点唯一的子节点;第三种是被删除的节点有两个子节点,这种情况是最麻烦的。我们采用的思想是将该节点的该节点右子树中最小的一个节点的值覆盖该节点中的值,然后再删除该节点的右子树中的最小的那个子节点。因为,该节点的右子树中的最小的那个子节点的值刚好大于被删除节点的左子树中所有的值,又小于被删除节点的右子树中所有的值。最小的那个子节点不可能有左子树,不然它就不是最小的节点,删除该节点就转换为删除一个只有一个子节点的节点,即第二种情况。

《C++实现二叉树的插入、删除、查询、遍历》

《C++实现二叉树的插入、删除、查询、遍历》

                         第二种情况(删除节点7)



《C++实现二叉树的插入、删除、查询、遍历》

《C++实现二叉树的插入、删除、查询、遍历》 

                     第三种情况(删除节点5)

                     其实是将5的那个节点赋值为6,然后删除节点6.

代码:

template< typename T>
void BinaryTree<T>::remove(const T &theElement, BinaryNode * &t ) {
    if( nullptr == t ) {
        return;
    } else {
        if ( theElement < t->element) {
            remove(t->leftNode);
        } else if ( theElement > t->element ) {
            remove (t->rightNode);
        } else if (nullptr != t->leftNode && nullptr != t->rightNode ) {  //需要删除的节点两个儿子

             t->element = findMin(t->rightNode)->element;
            remove(t->element, t->rightNode);
        } else {
            BinaryNode * oldNode = t;
            t = ( nullptr!= t->leftNode) ? t->leftNode : t->rightNode;
            delete oldNode;
        }
    }
};

  

template< typename T>
typename BinaryTree<T>::BinaryNode * BinaryTree<T>::findMin(BinaryNode *bNode) const {
    if ( nullptr!= bNode) {
        while( nullptr != bNode->leftNode) {
            bNode = bNode->leftNode;
        }
    }

    return bNode;
}



整个二叉树的工程文件在本人github上可以查阅:

https://github.com/yuanzoudetuzi/binaryTree 


    原文作者:远走的兔子
    原文地址: https://blog.csdn.net/u014182411/article/details/69831492
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞