二叉树常见的面试题—C++实现

  • 1. 前序/中序/后序遍历(递归&非递归)
  • 2. 层序遍历
  • 3. 求二叉树的高度
  • 4. 求叶子节点的个数
  • 5. 求二叉树第k层的节点个数
  • 6. 判断一个节点是否在一棵二叉树中
  • 7. 求两个节点的最近公共祖先
  • 8. 判断一棵二叉树是否是平衡二叉树
  • 9. 求二叉树中最远的两个节点的距离
  • 10. 由前序遍历和中序遍历重建二叉树(前序序列:1 2 3 4 5 6 – 中序序列:3 2 4 1 6 5)
  • 11. 判断一棵树是否是完全二叉树
  • 12. 求二叉树的镜像
  • 13. 将二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

1. 前序/中序/后序遍历(递归&非递归)

前序遍历(先根遍历) :
  (1)先访问根节点  (2)前序访问左子树(3)前序访问右子树【1 2 3 4 5 6】

中序遍历:
  (1)中序访问左子树 (2)访问根节点   (3)中序访问右子树【3 2 4 1 6 5】

后序遍历(后根遍历):
  (1)后序访问左子树 (2)后序访问右子树(3)访问根节点【3 4 2 6 5 1】

 层序遍历: 一层层节点依次遍历。 【1 2 5 3 4 6】

一. 递归: 将其化为子问题。实现简单但是它也是有缺陷的,它的缺陷是递归每次需要压栈,而栈的空间相对来说不是很大,在多线程中只有10几M,如果在极端情况下这个二叉树深度很大,则会有栈溢出的问题产生。

二 . 非递归:

前序遍历:
要借助栈的后进先出的特性来完成。 我们将1,2,3分别压栈,3的左为空则停止压栈,取栈顶元素3存放在变量top中,并删除栈顶元素,访问top的右子树,它的右子树也为空,则取栈顶元素2并访问它的右子树,直至栈为空(中序遍历思想相同)
后序遍历:
我们要先访问了左子树和右子树才能访问根。所以我们在这里增加了一个变量prev,它用于记录上一个走过的结点 。

//前序遍历(中左右)——————递归
    void PrevOrder()
    {
        _PrevOrder(_root);
    }

    void _PrevOrder(Node* root)
    {
        if(root == NULL)
            return;
        cout<<root->_data<<" ";//中
        _PrevOrder(root->_left);//左
        _PrevOrder(root->_right);//右
    }

//________________________________________________

    //前序遍历(中左右)——————非递归

//________________________________________________

void PrevOrderNonR()
{
    Node* cur = _root;
    stack<Node*> s;
    while( cur || !s.empty())
    {
        while(cur)
        {
            cout<<cur->_data<<" ";
            s.push(cur);
            cur = cur->_left;
        }//中左
        Node* top = s.top();
        s.pop();
        cur = top->_right;
    }
    cout<<endl;
}

//中序遍历(左中右)————————递归
    void InOrder()
    {
        _InOrder(_root);
    }

    void _InOrder(Node* root)
    {
        if(root == NULL)
            return;
        _InOrder(root->_left);//左
        cout<<root->_data<<" ";//中
        _InOrder(root->_right);//右
    }

//________________________________________________

        //中序遍历(左中右)——————非递归
//________________________________________________
    void InOrderNonR()
    {
        Node* cur = _root;
        stack<Node*> s;
        while( cur || !s.empty() )
        {
            while(cur)
            {
                s.push(cur);
                cur = cur->_left;
            }
            Node* top = s.top();
            cout<<top->_data<<" ";
            s.pop();
            cur = top->_right;
        }
        cout<<endl;
    }

//________________________________________________

        //后序遍历(左右中)——————递归
//________________________________________________

    void PostOrder()
    {
        _PostOrder(_root);
    }

    void _PostOrder(Node* root)
    {
        if(root == NULL)
            return;
        _PostOrder(root->_left);//左
        _PostOrder(root->_right);//右
        cout<<root->_data<<" ";//中
    }

//________________________________________________

        //后序遍历(左右中)——————非递归
//________________________________________________
void PostOrderNonR()
{
    Node* cur = _root;
    stack<Node*> s;
    Node* prev = NULL;
    while( cur || !s.empty() )
    {
        while(cur)
        {
            s.push(cur);
            cur = cur->_left; // 左
        }
        Node* top = s.top();
        if(top->_right == NULL || top->_right == prev)// 如果当前节点的右子树为空 或者它的前一个节点为它的右节点(证明右节点已经访问过了),即打印当前节点的值(右中)
        {
            cout<<top->_data<<" ";
            s.pop();
            prev = top;
        }
        else
            cur = top->_right;// 右
    }
    cout<<endl;
}

OJ下实现前序遍历:binary-tree-preorder-traversal

Given a binary tree, return the preorder traversal of its nodes’ values. For example: Given binary tree{1,#,2,3},return[1,2,3]. Note: Recursive solution is trivial, could you do it iteratively?

class Solution {
public:
    void _preorderTraversal(TreeNode *root,vector<int>& V){
        if(root == NULL)
            return;
        V.push_back(root->val);
        _preorderTraversal(root->left,V);
        _preorderTraversal(root->right,V);
    }
    vector<int> preorderTraversal(TreeNode *root) {
        //前序遍历二叉树 中左右
        vector<int> V;
        _preorderTraversal(root,V);
        return V;
    }
};

OJ下实现后序遍历:binary-tree-postorder-traversal

/** * Definition for binary tree * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */
class Solution {
public:
    void _postorderTraversal(TreeNode *root,vector<int> &V){
        if(root == NULL)
            return;
        _postorderTraversal(root->left,V);
        _postorderTraversal(root->right,V);
        V.push_back(root->val);
    }
    vector<int> postorderTraversal(TreeNode *root) {
        vector<int> V;
        _postorderTraversal(root,V);
        return V;
    }
};

2. 层序遍历—广度优先遍历

思想:借助队列的先进先出。
先将根节点root(1)入队,然后拿出根节点,并且将根节点的左右孩子入队(2 5);再将队头节点(2)拿出来,将队头的左右孩子(3 4)入队,以此类推,直至队列为空,循环终止。
《二叉树常见的面试题—C++实现》

//层序遍历——广度优先遍历
    void LevelOrder()
    {
        queue<Node*> q;
        if(_root)
            q.push(_root);
        while( !q.empty() )// 终止条件:队列为空
        {
            Node* front = q.front();
            cout<<front->_data<<" ";
            q.pop();
            // 队头pop(),将其左右非空节点入队
            if(front->_left)
                q.push(front->_left);
            if(front->_right)
                q.push(front->_right);
        }
        cout<<endl;
    }

3. 求二叉树的高度

思想:化为子问题:比较左右子树的高度,高的树的depth+1就是二叉树的高度。(其中1是根节点,第一层)

//求高度
    size_t Depth()
    {
        return _Depth(_root);
    }
    size_t _Depth(Node* root)
    {
        if(root == NULL)
            return 0;
        size_t leftDepth = _Depth(root->_left);
        size_t rightDepth = _Depth(root->_right);
        return leftDepth > rightDepth ? leftDepth+1 : rightDepth+1;
    }

OJ下的实现:

class Solution { public: int TreeDepth(TreeNode* pRoot) { if(pRoot == NULL) return 0; return max((1+TreeDepth(pRoot->left)),(1+TreeDepth(pRoot->right))); } };

4. 求叶子节点的个数

//求树中叶子节点
//方法一:————化成子问题(左子树和右子树都为空,则返回1   其结果就是:左边叶子节点+右边叶子节点)
    /*int LeafSize()
    {
        return _LeafSize(_root);
    }
    size_t _LeafSize(Node* root)
    {
        if(root == NULL)
            return 0;
        if(root->_left == NULL && root->_right == NULL)
        {
            return 1;
        }
        return _LeafSize(root->_left)+_LeafSize(root->_right);
    }*/
//方法二:遍历
    size_t LeafSize()
    {
        size_t size = 0;
        _LeafSize(_root,size);
        return size;
    }
    void _LeafSize(Node* root,size_t& size)
    {
        if(root == NULL)
            return;
        if(root->_left == NULL && root->_right == NULL)
            ++size;
        _LeafSize(root->_left,size);
        _LeafSize(root->_right,size);
    }

5. 求二叉树第k层的节点个数

//________________________________________________

 //求第k层的结点数——————————————————递归(子问题)

//________________________________________________

size_t GetKLevelSize(size_t k)
{
 return _GetKLevelSize(_root,k);
}
size_t _GetKLevelSize(Node* root,size_t k)
{
 if(root == NULL)
 return NULL;
 if(k == 1)
 return 1;
 return _GetKLevelSize(root->_left,k-1)+ _GetKLevelSize(root->_right,k-1); 
}

6. 判断一个节点是否在一棵二叉树中

思想:如果根节点就是该节点则返回true;如果不是,就在该树的左子树找,找不到就在右子树找,直到找到为止。而如果都没有的话,就证明该节点不在这棵树中。

//________________________________________________

      //6. 判断一个节点是否在一棵二叉树中

//________________________________________________
bool FindKey(const T& key)
{
    return _FindKey(_root,key);
}
bool _FindKey(Node* _root,const T& key)
{
    if(_root == NULL)
        return false;
    if(_root->_data == key)
        return true;
    // 子问题
    if(_FindKey(_root->_left,key))
        return true;
    if(_FindKey(_root->_right,key))
        return true;
    return false;
}

7. 求两个节点的最近公共祖先

思想:自己也是自己的祖先。(因为如果一个节点是root节点,那么他们的最近公共祖先就是根节点)
第一种情况:两个节点中,如果其中一个节点是根节点,则最近公共祖先就是根节点root
第二种情况:两个节点,一个在左子树,一个在右子树,两个节点的最近公共祖先是根节点root
第三种情况:如果都在左子树或者右子树,则化为子问题,继续重复第一或者第二种情况。
而这种方法的时间复杂度是:O(n^2),所以我们应该对其做优化,使其时间复杂度为:O(n)

//________________________________________________

      // 7. 求两个节点的最近公共祖先

//________________________________________________
// 时间复杂度为O(n^2)
    Node* LowestCommonAncestors(const T& x1,const T& x2)
    {
        return GetLCA(_root,x1,x2);
    }

    Node* GetLCA(Node* root,const T& x1,const T& x2)
    {
        if(root == NULL)
            return NULL;
        // 1. 其中一个节点如果是根节点,则最近公共祖先就是根节点root
        if(x1 == root->_data || x2 == root->_data )
            return root;
        // 判断两个节点在左子树还是右子树

        bool x1Inleft,x1Inright,x2Inleft,x2Inright;
        x1Inleft = _FindKey(root->_left,x1);
        if( x1Inleft == false)
        {
             x1Inright = true;
        }
        else
        {
            x1Inright = false;
        }

        x2Inleft = _FindKey(root->_left,x2);
        if(x2Inleft == false)
        {
            x2Inright = true;
        }
        else
        {
             x2Inright = false;
        }
        // 2. 两个节点,一个在左子树,一个在右子树,两个节点的最近公共祖先是根节点root

        if( (x1Inleft&&x2Inright) || (x2Inleft&&x1Inright) )
            return root;
        // 3. 如果都在左子树或者右子树,则化为子问题

        if( x1Inleft&&x2Inleft )
            return GetLCA(root->_left,x1,x2);
        else 
            return GetLCA(root->_right,x1,x2);
    }

8. 判断一棵二叉树是否是平衡二叉树

思想:平衡二叉树的条件就是:1. 左右子树的高度之差的绝对值 <= 1 2.并且左右子树都是平衡二叉树
其主要的方法请看我的另外一篇博客:
https://blog.csdn.net/qq_37941471/article/details/79748878

//________________________________________________

      // 8. 判断一棵树是否是平衡二叉树

//________________________________________________

//1. 递归——时间复杂度:O(n^2)
// (递归的次数*每次递归的次数)
// 每个节点的遍历*高度(也是遍历整个树)

  // bool IsBalance() 
  // { 
  // int depth = 0;
  // return _IsBalance(_root); 
  // } 
  // int MaxDepth(Node* root) 
  // { 
  // if (NULL == root) 
  // return 0; 
  // int left = MaxDepth(root->_left)+1; 
  // int right = MaxDepth(root->_right) + 1; 
  // return left > right ? left : right; 
  // } 
  // bool _IsBalance(Node* root)
  // {
  // //递归的终止条件
  // if(root == NULL)
  // {
  // return true;
  // }
  // int leftHeight = MaxDepth(root->_left);
  // int rightHeight = MaxDepth(root->_right);
  // return abs(rightHeight-leftHeight) < 2
  // && _IsBalance(root->_left)
  // && _IsBalance(root->_right);
  // }

    //2. 优化——时间复杂度O(n)——高度只遍历一次
      bool IsBalance() 
      {  
         int depth = 0;
         return _IsBalance(_root,depth);  
      } 
        bool _IsBalance(Node* root,int& depth)
        {
            if(root == NULL)
            {
                return true;
            }
            int left = 0;
            int right = 0;
            if(_IsBalance(root->_left,left)&&_IsBalance(root->_right,right))
            {
                if( abs(left-right) > 1 )
                    return false;
                depth = (left > right ? left : right)+1;
                return true;
            }
            return false;
        }

OJ下实现平衡二叉树:

必须用优化后的代码,时间复杂度为O(n)

class Solution {
public:
    bool IsBalanced(TreeNode* root,int& depth){
        if(root == NULL){
            return true;
        }
        int left = 0;
        int right = 0;
        if(IsBalanced(root->left,left)&&IsBalanced(root->right,right)){
            if( abs(left-right) > 1 )
                return false;
            depth = (left > right ? left : right)+1;
            return true;
        }
        return false;
    }
    bool IsBalanced_Solution(TreeNode* pRoot) {
        int depth = 0;
        return IsBalanced(pRoot,depth);
    }
};

9. 求二叉树中最远的两个节点的距离

10. 由前序遍历和中序遍历重建二叉树(前序序列:1 2 3 4 5 6 – 中序序列:3 2 4 1 6 5)

11. 判断一棵树是否是完全二叉树

12. 求二叉树的镜像

13. 将二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

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