二叉树基本操作以及面试题

二叉树概念

二叉树 : 一棵二叉树是结点的一个有限集合。该集合或者为空,或者是由一个根节点加上两棵分别称为左子树和右子树的二叉树组成。也就是说,二叉树不存在度大于2的节点(分支数最大不超过2), 且有左右之分,顺序不可颠倒。

由此,当我们创建二叉树的时候,就要考虑到左右分支的存储问题。也就是说,定义节点时, 除了数据以外,要定义两个指针分别指向其左右节点。

  • 二叉树的存储结构主要有三种:顺序存储、链式存储和仿真指针(静态链表)存储结构。此处采用链式存储, 对其他两种存储方式不做介绍。
  • 下面以一颗二叉树为例,进行各种操作。

这里写图片描述

节点构造

综上,可将二叉树节点定义如下:

template<typename T>
struct BinaryTreeNode
{
    BinaryTreeNode(const T& value)
        : _left(NULL)
        , _right(NULL)
        , _value(value)
    {}
    BinaryTreeNode* _left;
    BinaryTreeNode* _right;
    T _value;
};

二叉树框架

template<typename T>
class BinaryTree
{
public:
    typedef BinaryTreeNode<T> Node;
private:
    Node* _pRoot; //根节点
};

面试题

1.二叉树的创建——即构造函数

按照前序遍历的顺序来创建,即按照 根–>左子树–>右子树的顺序。上述二叉树的节点构造顺序为

这里写图片描述

很显然,这个问题用递归来解决再简单不过。每遇到一个节点都当做是一棵树来处理,当遇到度为0的节点(arr[index] == invalid)时,即为递归出口。

下面来看代码:

    //默认构造函数,初始化根节点为空
    BinaryTree()
        :_pRoot(NULL)
    {}
    //数组arr用来存储各节点的值
    //size表示数组大小
    //度为0的节点的左右子树以invalid无效值标记,当遇到invalid时表示当前位置无效,即不存在
    BinaryTree(T* arr, size_t size, const T& invalid)
    {
        //创建二叉树时,从根节点开始,index表示数组下标,即arr[index]表示当前要创建的节点的值
        size_t index = 0;
        CreateBinaryTree(_pRoot, arr, size, invalid, index);
    }
    //注意:根节点应为Node*的&类型,否则函数返回后该函数的栈帧被销毁,相当于未创建。(有关函数栈帧的问题,请参见博客[函数的栈帧结构](http://blog.csdn.net/jenaeli/article/details/53284408))
    //下标index也应为&,递归调用时应当传址调用
    void CreateBinaryTree(Node* &pRoot, const T* arr, size_t size, const T& invalid, size_t& index)
    {
        //若当前下标合法且值合法,创建当前位置节点
        if (index < size && arr[index] != invalid)
        {
            pRoot = new Node(*(arr + index));
            //递归创建左子树
            CreateBinaryTree(pRoot->_left, arr, size, invalid, ++index);
            //递归创建右子树
            CreateBinaryTree(pRoot->_right, arr, size, invalid, ++index);
        }
    }

下面画一张图来展示递归过程

这里写图片描述

2.拷贝——拷贝构造函数

话不多说,直接来看代码吧

    //深拷贝
    BinaryTree(const BinaryTree<T>& b)
    {
        _pRoot = _CopyBinaryTree(b._pRoot);
    }
    Node* _CopyBinaryTree(Node* pRoot)
    {
        Node* pCur = NULL;
        if (pRoot)
        {
            pCur = new Node(pRoot->_value);
            pCur->_left = _CopyBinaryTree(pRoot->_left);
            pCur->_right = _CopyBinaryTree(pRoot->_right);
        }
        return pCur;
    }

需要提到的是,这里拷贝时需要深拷贝,以免造成多个指针指向同一块空间的情况。

3.销毁——析构函数

    ~BinaryTree()
    {
        _DestroyBinaryTree(_pRoot);
    }
    //先找到最左边的叶子结点然后依次向上销毁,若先销毁根节点,则左右子树节点无法销毁。即销毁时按照由左到右,由下向上的顺序销毁。
    void _DestroyBinaryTree(Node* pRoot)
    {
        if (pRoot == NULL)
            return;
        _DestroyBinaryTree(pRoot->_left);
        _DestroyBinaryTree(pRoot->_right);
        delete pRoot;
    }

也来看图吧

这里写图片描述

4.赋值运算符重载

这个很简单,只要拷贝和析构完成了,把两个适当组合一下就ok啦,来看代码:

    BinaryTree<T>& operator=(const BinaryTree<T>& b)
    {
        if (this != &b)
        {
            if (_pRoot)
                _DestroyBinaryTree(_pRoot);
            _pRoot = _CopyBinaryTree(b._pRoot);
        }
        return *this;
    }

5.前序遍历——递归,非递归

前序遍历:根–>左子树–>右子树
序列为:1 2 4 3 5 6
先来看递归的吧!

    void PreOrder()
    {
        cout << "PreOrder:" << endl;
        _PreOrder(_pRoot);
        cout << endl;
    }
    void _PreOrder(Node* pRoot)
    {
        if (pRoot)
        {
            //遇到一个根节点直接访问
            cout << pRoot->_value << "  ";
            //递归访问左子树
            _PreOrder(pRoot->_left);
            //递归访问右子树
            _PreOrder(pRoot->_right);
        }
    }

非递归

void PreOrderNor()
    {
        if (_pRoot)
        {
            stack<Node*> s;
            //根节点压栈保存
            s.push(_pRoot);
            while (!s.empty())
            {
                //取栈顶指针,访问数据,然后出栈
                Node* pCur = s.top();
                cout << pCur->_value << "  ";
                s.pop();
                //右子树压栈
                if (pCur->_right)
                    s.push(pCur->_right);
                //左子树压栈
                //栈内元素先进后出,右节点先压栈保证先访问左节点
                if (pCur->_left)
                    s.push(pCur->_left);
            }
            cout << endl;
        }
    }

6.中序遍历——递归,非递归

中序遍历:左子树–>根–>右子树
序列:4 2 1 5 3 6
递归代码如下:

    //参见前序,不多做介绍
    void InOrder()
    {
        cout << "InOrder:" << endl;
        _InOrder(_pRoot);
        cout << endl;
    }

    void _InOrder(Node* pRoot)
    {
        if (pRoot)
        {
            _InOrder(pRoot->_left);
            cout << pRoot->_value << "  ";
            _InOrder(pRoot->_right);
        }
    }

非递归代码

    void InOrderNor()
    {
        if (_pRoot)
        {
            stack<Node*> s;
            Node* pCur = _pRoot;
            while (pCur || !s.empty())
            {
                //寻找最左边的节点,并保存路径上的所有节点
                while (pCur)
                {
                    s.push(pCur);
                    pCur = pCur->_left;
                }
                pCur = s.top();
                cout << pCur->_value << "  ";
                s.pop();
                //解决连续左分支问题
                while (pCur->_right == NULL && !s.empty())
                {
                    pCur = s.top();
                    cout << pCur->_value << "  ";
                    s.pop();
                }

                pCur = pCur->_right;
            }
            cout << endl;
        }
    }

7.后序遍历——递归, 非递归

后序遍历:左子树–>右子树–>根
序列:4 2 5 6 3 1
递归代码:

    void PostOrder()
    {
        cout << "PostOrder:" << endl;
        _PostOrder(_pRoot);
        cout << endl;
    }
    //参见前序
    void _PostOrder(Node* pRoot)
    {
        if (pRoot)
        {
            _PostOrder(pRoot->_left);
            _PostOrder(pRoot->_right);
            cout << pRoot->_value << "  ";
        }
    }

非递归代码

    void PostOrderNor()
    {
        if (_pRoot)
        {
            stack<Node*> s;
            Node* pCur = _pRoot;
            //起标记作用,判断右节点是否已访问
            Node* Prev = NULL;
            while (pCur || !s.empty())
            {
                //寻找最左边节点
                while (pCur)
                {
                    s.push(pCur);
                    pCur = pCur->_left;
                }
                Node* pTop = s.top();
                //若右节点不存在或者右节点已访问,则访问当前栈顶节点,即当前右节点的根节点,然后出栈
                if ((NULL == pTop->_right) || (pTop->_right == Prev))
                {
                    cout << pTop->_value << "  ";
                    Prev = pTop;
                    s.pop();
                }
                else
                    pCur = pTop->_right;
            }
            cout << endl;
        }
    }

8.层序遍历

层序遍历序列:1 2 3 4 5 6
层序遍历:按照从上到下,从左到右的顺序遍历
代码如下:

    //调用库——队列queue——先进先出
    //按照从左到右从上向下的顺序入队列
    void LevelOrder()
    {
        cout << "LevelOrder:" << endl;
        if (_pRoot)
        {
            queue<Node*> q;
            q.push(_pRoot);
            while (!q.empty())
            {
                Node* pCur = q.front();
                cout << pCur->_value << "  ";
                if (pCur->_left)
                    q.push(pCur->_left);
                if (pCur->_right)
                    q.push(pCur->_right);
                q.pop();
            }
        }
        cout << endl;
    }

9.二叉树的镜像

镜像:根不变,左右子树交换
递归代码如下:

    void BinaryMirror()
    {
        _BinaryMirror(_pRoot);
    }
    Node* _BinaryMirror(Node* pRoot)
    {
        //递归出口
        if (pRoot == NULL)
            return NULL;
        //交换左右子树
        swap(pRoot->_left, pRoot->_right);
        //递归求左子树镜像
        _BinaryMirror(pRoot->_left);
        //递归求右子树镜像
        _BinaryMirror(pRoot->_right);
    }

非递归代码

    void BinaryMirror_Nor()
    {
        if (_pRoot)
        {
            queue<Node*> q;
            q.push(_pRoot);
            while (!q.empty())
            {
                Node* pCur = q.front();
                if (pCur->_left)
                    q.push(pCur->_left);
                if (pCur->_right)
                    q.push(pCur->_right);
                swap(pCur->_left, pCur->_right);
                q.pop();
            }
        }
    }

10.查找节点

直接来看代码吧,不解释

    Node* Find(const T& value)
    {
        return _Find(_pRoot, value);
    }
    Node* _Find(Node* pRoot, const T& value)
    {
        //递归出口
        if (NULL == pRoot)
            return NULL;
        //查看根节点是否是要查找的节点
        if (value == pRoot->_value)
            return pRoot;
        //左递归继续查找
        Node* pCur = _Find(pRoot->_left, value);
        if (pCur)
            return pCur;
        //右递归
        return _Find(pRoot->_right, value);
    }

11.寻找双亲节点

双亲节点即当前节点的前驱节点
看代码吧

    Node* Parent(Node* pCur)
    {
        return _Parent(_pRoot, pCur);
    }
    Node* _Parent(Node* pRoot, Node* pCur)
    {
        //递归出口,若pCur就是根节点,则直接返回NULL,根节点无双亲
        if (NULL == pRoot || pRoot == pCur)
            return NULL;
        //判断根pRoot左右节点是否是pCur,若是返回pRoot
        if (pRoot->_left == pCur)
            return pRoot;
        if (pRoot->_right == pCur)
            return pRoot;
        //左右递归查找
        Node* par = _Parent(pRoot->_left, pCur);
        if(par)
            return par;
        return _Parent(pRoot->_right, pCur);
    }

12.求树的高度

只有一个根节点时,高度为1
代码如下:

    size_t Height()
    {
        return _Height(_pRoot);
    }
    size_t _Height(Node* pRoot)
    {
        if (pRoot == NULL)
            return 0;
        //记录左子树的高度
        size_t DeepLeft1 = _Height(pRoot->_left);
        //几楼左子树的深度
        size_t DeepLeft2 = _Height(pRoot->_right);
        //返回左右子树较大者+1
        return (DeepLeft1 > DeepLeft2) ? DeepLeft1 + 1 : DeepLeft2 + 1;
    }

13.求叶节点的个数

引用块内容

    size_t GetLeefCount()
    {
        return _GetLeefCount(_pRoot);
    }
    size_t _GetLeefCount(Node* pRoot)
    {
        //递归出口
        if (pRoot == NULL)
            return 0;
        //若无左右子树,返回1
        if (pRoot->_left == NULL && NULL == pRoot->_right)
            return 1;
        //左右子树的叶节点个数相加后返回
        return _GetLeefCount(pRoot->_left) + _GetLeefCount(pRoot->_right);
    }

14.求第K层的节点个数

引用块内容

    size_t GetKLevelCount(size_t k)
    { 
        return _GetKLevelCount(_pRoot, k);
    }
    size_t _GetKLevelCount(Node* pRoot, size_t k)
    {
        //递归出口
        if (pRoot == NULL || k < 1)
            return 0;
        if (k == 1)
            return 1;
        //返回第K层左右子树节点数之和
        return _GetKLevelCount(pRoot->_left, k-1) + _GetKLevelCount(pRoot->_right, k-1);
    }

15.寻找左右孩子

寻找左孩子

    Node* GetLeftChild(Node* pCur)
    {
        return _GetLeftChild(_pRoot, pCur);
        //return (pCur == NULL) ? NULL : pCur->_left;
    }
    Node* _GetLeftChild(Node* pRoot, Node* pCur)
    {
        //递归出口
        if (NULL == pRoot)
            return NULL;
        //若根节点就是pCur且右孩子存在,则返回
        if (pRoot == pCur && pRoot->_left)
            return pRoot->_left;
        //左右递归查找
        Node* pLeftChild = _GetLeftChild(pRoot->_left, pCur);
        if (pLeftChild)
            return pLeftChild;
        return _GetLeftChild(pRoot->_right, pCur);
    }

寻找右孩子

    Node* GetRightChild(Node* pCur)
    {
        //若pCur为空则返回空,否则返回pCur的右孩子(若pCur无右孩子,则右孩子为空)
        return (pCur == NULL) ? NULL : pCur->_right;
    }
  • 以上所有代码编译环境为VS2013
    以上内容如有错误,欢迎不吝指出!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值