第八章

二叉树

8.1  基本概念

树是一种非线性的数据结构,它在客观世界中广泛存在,例如人类社会的族谱和各种社会组织机构都可以用树来表示。我们最常用到的是树和二叉树,其中又以二叉树更为实用。为什么这样说呢?因为大部分的操作都可以转变为一个父亲、一个左儿子和一个右儿子来实现,而且对二叉树的操作更为简单。

8.2  代码实现

二叉树的代码实现如下:

///////////////////////////////////////////////////////////////////////////////
//
//  FileName    :   btree.h
//  Version     :   0.10
//  Author      :   Luo Cong
//  Date        :   2005-1-12 12:22:40
//  Comment     :  
//
///////////////////////////////////////////////////////////////////////////////

#ifndef __BINARY_TREE_H__
#define __BINARY_TREE_H__

#include <assert.h>
#include <crtdbg.h>

#ifdef _DEBUG
#define DEBUG_NEW new (_NORMAL_BLOCK, THIS_FILE, __LINE__)
#endif

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#ifdef _DEBUG
#ifndef ASSERT
#define ASSERT  assert
#endif
#else   // not _DEBUG
#ifndef ASSERT
#define ASSERT
#endif
#endif  // _DEBUG

template<typename T>
class CBTNode
{
public:
    T data;
    CBTNode<T> *parent;
    CBTNode<T> *left;
    CBTNode<T> *right;
    CBTNode(
        T data = T(),
        CBTNode<T> *parent = NULL,
        CBTNode<T> *left = NULL,
        CBTNode<T> *right = NULL
    ) : data(data), parent(parent), left(left), right(right) {}
};

template<typename T>
class CBTree
{
protected:
    CBTNode<T> *m_pNodeRoot;

public:
    CBTree(CBTNode<T> *initroot = NULL);
    ~CBTree();
    void AssignTo(CBTNode<T> *p);
    void Copy(CBTree<T> &p);

private:
    CBTNode<T>* Copy(CBTNode<T> *p);

    void DestroyNode(CBTNode<T> *p);

    void PreOrderTraverse(
        const CBTNode<T> *p,
        void (*Visit)(const T &data)
    ) const;

    void InOrderTraverse(
        const CBTNode<T> *p,
        void (*Visit)(const T &data)
    ) const;

    void PostOrderTraverse(
        const CBTNode<T> *p,
        void (*Visit)(const T &data)
    ) const;

    void GetNodesCount(const CBTNode<T> *p, unsigned int *unCount) const;

    void GetLeafCount(const CBTNode<T> *p, unsigned int *unCount) const;

public:
    T&              GetNodeData(CBTNode<T> *p);
    T               GetNodeData(const CBTNode<T> *p) const;
    void            SetNodeData(CBTNode<T> *p, const T &data);
    CBTNode<T>*&    GetRoot();
    CBTNode<T>*     GetRoot() const;
    CBTNode<T>*&    GetParent(CBTNode<T> *p);
    CBTNode<T>*     GetParent(const CBTNode<T> *p) const;
    CBTNode<T>*&    GetLeftChild(CBTNode<T> *p);
    CBTNode<T>*     GetLeftChild(const CBTNode<T> *p) const;
    CBTNode<T>*&    GetRightChild(CBTNode<T> *p);
    CBTNode<T>*     GetRightChild(const CBTNode<T> *p) const;
    CBTNode<T>*&    GetLeftSibling(CBTNode<T> *p);
    CBTNode<T>*     GetLeftSiblig(const CBTNode<T> *p) const;
    CBTNode<T>*&    GetRightSibling(CBTNode<T> *p);
    CBTNode<T>*     GetRightSibling(const CBTNode<T> *p) const;

public:
    int             IsEmpty() const;
    void            Destroy();
    void            PreOrderTraverse(void (*Visit)(const T &data)) const;
    void            InOrderTraverse(void (*Visit)(const T &data)) const;
    void            PostOrderTraverse(void (*Visit)(const T &data)) const;
    unsigned int    GetNodesCount() const; // Get how many nodes
    unsigned int    GetLeafCount() const;
    unsigned int    GetDepth() const;
    unsigned int    GetDepth(const CBTNode<T> *p) const;
};

template<typename T>
inline CBTree<T>::CBTree(CBTNode<T> *initroot) : m_pNodeRoot(initroot)
{
}

template<typename T>
inline CBTree<T>::~CBTree()
{
    Destroy();
}

template<typename T>
inline void CBTree<T>::AssignTo(CBTNode<T> *p)
{
    ASSERT(p);
    m_pNodeRoot = p;
}

template<typename T>
inline void CBTree<T>::Copy(CBTree<T> &p)
{
    if (NULL != p.m_pNodeRoot)
        m_pNodeRoot = Copy(p.m_pNodeRoot);
    else
        m_pNodeRoot = NULL;
}

template<typename T>
inline CBTNode<T>* CBTree<T>::Copy(CBTNode<T> *p)
{
    if (p)
    {
        CBTNode<T> *pNewNode = new CBTNode<T>;
        if (NULL == pNewNode)
            return NULL;
        pNewNode->data = p->data;
        pNewNode->parent = p->parent;
        pNewNode->left = Copy(p->left);
        pNewNode->right = Copy(p->right);
        return pNewNode;
    }
    else
        return NULL;
}

template<typename T>
inline CBTNode<T>*& CBTree<T>::GetLeftChild(CBTNode<T> *p)
{
    ASSERT(p);
    return *(&(p->left));
}

template<typename T>
inline CBTNode<T>* CBTree<T>::GetLeftChild(const CBTNode<T> *p) const
{
    ASSERT(p);
    return p->left;
}

template<typename T>
inline CBTNode<T>*& CBTree<T>::GetRightChild(CBTNode<T> *p)
{
    ASSERT(p);
    return *(&(p->right));
}

template<typename T>
inline CBTNode<T>* CBTree<T>::GetRightChild(const CBTNode<T> *p) const
{
    ASSERT(p);
    return p->right;
}

template<typename T>
inline CBTNode<T>*& CBTree<T>::GetLeftSibling(CBTNode<T> *p)
{
    ASSERT(p);

    if (p->parent)
        return *(&(p->parent->left));
    else
        return *(&(p->parent)); // return NULL;
}

template<typename T>
inline CBTNode<T>* CBTree<T>::GetLeftSiblig(const CBTNode<T> *p) const
{
    ASSERT(p);

    if (p->parent)
        return p->parent->left;
    else
        return p->parent;       // return NULL;
}

template<typename T>
inline CBTNode<T>*& CBTree<T>::GetRightSibling(CBTNode<T> *p)
{
    ASSERT(p);

    if (p->parent)
        return *(&(p->parent->right));
    else
        return *(&(p->parent)); // return NULL;
}

template<typename T>
inline CBTNode<T>* CBTree<T>::GetRightSibling(const CBTNode<T> *p) const
{
    ASSERT(p);

    if (p->parent)
        return p->parent->right;
    else
        return p->parent;       // return NULL;
}

template<typename T>
inline CBTNode<T>*& CBTree<T>::GetParent(CBTNode<T> *p)
{
    ASSERT(p);
    return *(&(p->parent));
}

template<typename T>
inline CBTNode<T>* CBTree<T>::GetParent(const CBTNode<T> *p) const
{
    ASSERT(p);
    return p->parent;
}

template<typename T>
inline T& CBTree<T>::GetNodeData(CBTNode<T> *p)
{
    ASSERT(p);
    return p->data;
}

template<typename T>
inline T CBTree<T>::GetNodeData(const CBTNode<T> *p) const
{
    ASSERT(p);
    return p->data;
}

template<typename T>
inline void CBTree<T>::SetNodeData(CBTNode<T> *p, const T &data)
{
    ASSERT(p);
    p->data = data;
}

template<typename T>
inline int CBTree<T>::IsEmpty() const
{
    return NULL == m_pNodeRoot;
}

template<typename T>
inline CBTNode<T>*& CBTree<T>::GetRoot()
{
    return *(&(m_pNodeRoot));
}

template<typename T>
inline CBTNode<T>* CBTree<T>::GetRoot() const
{
    return m_pNodeRoot;
}

template<typename T>
inline void CBTree<T>::DestroyNode(CBTNode<T> *p)
{
    if (p)
    {
        DestroyNode(p->left);
        DestroyNode(p->right);
        delete p;
    }
}

template<typename T>
inline void CBTree<T>::Destroy()
{
    DestroyNode(m_pNodeRoot);
    m_pNodeRoot = NULL;
}

template<typename T>
inline void CBTree<T>::PreOrderTraverse(void (*Visit)(const T &data)) const
{
    PreOrderTraverse(m_pNodeRoot, Visit);
}

template<typename T>
inline void CBTree<T>::PreOrderTraverse(
    const CBTNode<T> *p,
    void (*Visit)(const T &data)
) const
{
    if (p)
    {
        Visit(p->data);
        PreOrderTraverse(p->left, Visit);
        PreOrderTraverse(p->right, Visit);
    }
}

template<typename T>
inline void CBTree<T>::InOrderTraverse(void (*Visit)(const T &data)) const
{
    InOrderTraverse(m_pNodeRoot, Visit);
}

template<typename T>
inline void CBTree<T>::InOrderTraverse(
    const CBTNode<T> *p,
    void (*Visit)(const T &data)
) const
{
    if (p)
    {
        InOrderTraverse(p->left, Visit);
        Visit(p->data);
        InOrderTraverse(p->right, Visit);
    }
}

template<typename T>
inline void CBTree<T>::PostOrderTraverse(void (*Visit)(const T &data)) const
{
    PostOrderTraverse(m_pNodeRoot, Visit);
}

template<typename T>
inline void CBTree<T>::PostOrderTraverse(
    const CBTNode<T> *p,
    void (*Visit)(const T &data)
) const
{
    if (p)
    {
        PostOrderTraverse(p->left, Visit);
        PostOrderTraverse(p->right, Visit);
        Visit(p->data);
    }
}

template<typename T>
inline unsigned int CBTree<T>::GetNodesCount() const
{
    unsigned int unCount;
    GetNodesCount(m_pNodeRoot, &unCount);
    return unCount;
}

template<typename T>
inline void CBTree<T>::GetNodesCount(
    const CBTNode<T> *p,
    unsigned int *unCount
) const
{
    ASSERT(unCount);

    unsigned int unLeftCount;
    unsigned int unRightCount;

    if (NULL == p)
        *unCount = 0;
    else if ((NULL == p->left) && (NULL == p->right))
        *unCount = 1;
    else
    {
        GetNodesCount(p->left, &unLeftCount);
        GetNodesCount(p->right, &unRightCount);
        *unCount = 1 + unLeftCount + unRightCount;
    }
}

template<typename T>
inline unsigned int CBTree<T>::GetLeafCount() const
{
    unsigned int unCount = 0;
    GetLeafCount(m_pNodeRoot, &unCount);
    return unCount;
}

template<typename T>
inline void CBTree<T>::GetLeafCount(
    const CBTNode<T> *p,
    unsigned int *unCount
) const
{
    ASSERT(unCount);

    if (p)
    {
        // if the node's left & right children are both NULL, it must be a leaf
        if ((NULL == p->left) && (NULL == p->right))
            ++(*unCount);
        GetLeafCount(p->left, unCount);
        GetLeafCount(p->right, unCount);
    }
}

template<typename T>
inline unsigned int CBTree<T>::GetDepth() const
{
    // Minus 1 here because I think the root node's depth should be 0.
    // So, don't do it if u think the root node's depth should be 1.
    return GetDepth(m_pNodeRoot) - 1;
}

template<typename T>
inline unsigned int CBTree<T>::GetDepth(const CBTNode<T> *p) const
{
    unsigned int unDepthLeft;
    unsigned int unDepthRight;

    if (p)
    {
        unDepthLeft = GetDepth(p->left);
        unDepthRight = GetDepth(p->right);
        return 1 +  // if don't plus 1 here, the tree's depth will be always 0
            (unDepthLeft > unDepthRight ? unDepthLeft : unDepthRight);
    }
    else
        return 0;
}

#endif  // __BINARY_TREE_H__

测试代码:

///////////////////////////////////////////////////////////////////////////////
//
//  FileName    :   btree.cpp
//  Version     :   0.10
//  Author      :   Luo Cong
//  Date        :   2005-1-12 13:17:07
//  Comment     :  
//
///////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include "btree.h"
using namespace std;

// 结点的数据类型
typedef char ElementType;

// 回调函数:Visit() = PrintElement()
static void PrintElement(const ElementType &data)
{
    cout << data;
}

int main()
{
    CBTNode<ElementType> *pRoot;
    CBTNode<ElementType> *pLeftChild;
    CBTNode<ElementType> *pRightChild;
    CBTree<ElementType> btree;

#ifdef _DEBUG
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

    pRoot = new CBTNode<ElementType>;
    if (NULL == pRoot)
        return EXIT_FAILURE;

    pLeftChild = new CBTNode<ElementType>;
    if (NULL == pLeftChild)
        return EXIT_FAILURE;

    pRightChild = new CBTNode<ElementType>;
    if (NULL == pRightChild)
        return EXIT_FAILURE;

    // 创建父亲结点
    pRoot->data = '+';
    pRoot->parent = NULL;
    pRoot->left = pLeftChild;
    pRoot->right = pRightChild;

    // 创建左儿子结点
    pLeftChild->data = 'a';
    pLeftChild->parent = pRoot;
    pLeftChild->left = NULL;
    pLeftChild->right = NULL;

    // 创建右儿子结点
    pRightChild->data = 'b';
    pRightChild->parent = pRoot;
    pRightChild->left = NULL;
    pRightChild->right = NULL;

    // 创建二叉树
    btree.AssignTo(pRoot);

    // 输出这棵二叉树
    cout << "   (" << btree.GetNodeData(btree.GetRoot()) << ")  " << endl;
    cout << "  /   \\ " << endl;
    cout << "(" << btree.GetNodeData(btree.GetLeftChild(btree.GetRoot()))
         << ")    (" << btree.GetNodeData(btree.GetRightChild(btree.GetRoot()))
         << ")" << endl << endl;

    cout << "这棵树的叶子数:" << btree.GetLeafCount() << endl;

    cout << "这棵树的深度是:" << btree.GetDepth() << endl;

    cout << "先序遍历:";
    btree.PreOrderTraverse(PrintElement);

    cout << endl << "中序遍历:";
    btree.InOrderTraverse(PrintElement);

    cout << endl << "后序遍历:";
    btree.PostOrderTraverse(PrintElement);

    cout << endl;

    return EXIT_SUCCESS;
}

8.3  说明

您也许已经注意到了一个“奇怪”的现象:在我的二叉树实现中,有各种对结点的访问操作(例如计算树的高、各种遍历),但就是没有插入和删除这两个操作的函数。其实这并不值得奇怪。因为二叉树基本上是一个最“底层”的类,将来我们在写二叉搜索树等更高级的类时,是要从二叉树开始继承的,而对于树这种非线性的数据结构来说,插入和删除是要根据它所处的环境来具体问题具体分析的——也就是说,没有一个特定的法则(这点不像链表,链表无论怎么变,它都是线性的)。所以,在具体的应用中,我才会给出具体的插入和删除代码。在这里,我用了一种很拙劣的方式来创建了一棵二叉树,请读者在这个问题上不要深究。

在结点类CBTNode中,我定义了4个成员变量:data、parent、left和right。data表示该结点的数据域,parent表示该结点的父亲结点,left和right分别表示该结点的左右儿子结点。这里要说明的是:

  1. parent指针并不是必需的,但有了它之后,就会大大简化许多对父亲结点的操作。因此,在资源并不十分紧张的情况下应该考虑加入它。

  2. 二叉树的根结点(root)的parent应该赋值为NULL

在二叉树中还大量运用了前面所说的一个强大的工具——递归。例如对二叉树的遍历操作就都是通过递归来实现的(不递归也行,可以用栈来模拟,但速度会比较慢,同时也多占用了很多空间,也就是说,非递归的算法无论是时间复杂度还是空间复杂度都比递归要高——非递归的唯一好处只是节省了堆栈。因此到底选用哪个,就要看具体的应用环境了)。另外,我在先序、中序和后序遍历中用了Visit()这个回调函数,这是为了增加处理的自由度。除此之外,我还写了几个要使用到遍历技术的子函数,如:GetLeafCount(),就是用先序遍历来获得二叉树的叶子个数。在此不一一而足,如有不清楚的地方,请联系我。

8.4  应用

基本的二叉树还谈不上有什么应用,因此我的示例程序只是做了一个对表达式的转换……您是不是想说,对表达式的转换不是在栈那里已经做过了吗?

是的!但实际上二叉树这种数据结构才是对表达式的最直观的储存和表达方式,甚至可以说,它天生就是一棵表达式!我的例子代码是用一棵二叉树来表示一个表达式:a + b,执行完后,会得到这样的输出结果:

   (+)
  /   \
(a)    (b)

这棵树的叶子数:2
这棵树的深度是:1
先序遍历:+ab
中序遍历:a+b
后序遍历:ab+