二叉树面试题汇总

二叉树节点定义如下:

struct BinaryTreeNode
{
    BinaryTreeNode(const T& data)
        : _data(data)
        , _pLeft(NULL)
        , _pRight(NULL)
    {}

    T _data;
    BinaryTreeNode<T>* _pLeft;    // 左孩子
    BinaryTreeNode<T>* _pRight;   // 右孩子
};

typedef BinaryTreeNode Node;

题目列表:

  1. 求二叉树中的节点个数
  2. 求二叉树的深度
  3. 前序遍历,中序遍历,后序遍历
    4.分层遍历二叉树(按层次从上往下,从左往右)
  4. 将二叉查找树变为有序的双向链表
  5. 求二叉树第K层的节点个数
  6. 求二叉树中叶子节点的个数
  7. 判断两棵二叉树是否结构相同
  8. 判断二叉树是不是平衡二叉树
  9. 求二叉树的镜像
  10. 求二叉树中两个节点的最低公共祖先节点
  11. 求二叉树中节点的最大距离
  12. 由前序遍历序列和中序遍历序列重建二叉树
    14.判断二叉树是不是完全二叉树

详细解答:

1. 求二叉树中的节点个数:

递归解法:
(1)如果二叉树为空,节点个数为0
(2)如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1

参考代码如下:

int GetNodeNum(Node * pRoot)  
    {  
        if(pRoot == NULL) // 递归出口  
            return 0;  
        return GetNodeNum(pRoot->_pLeft) + GetNodeNum(pRoot->_pRight) + 1;  
    } 
//非递归:
size_t GetNodeNum(Node* pRoot, size_t& count)
{
    if(pRoot==NULL)
        return 0;
    if(pRoot->_pLeft==NULL && pRoot->_pRight==NULL)
        return 1;
    int h = GetHight(pRoot);//求深度O(logN)
    size_t ret = 0;
    if((ret=GetHight(pRoot->_pRight)+1)==h)//左子树为满二叉树,用公式计算,右子树遍历计算
    {
        count = ((1<<(h-1))-1+1+_GetAllNode(pRoot->_pRight,count));//O(logN)
    }
    else
    {
        count = ((1<<(h-2))-1+1+_GetAllNode(pRoot->_pLeft,count));//右子树为满二叉树,用公式计算,左子树遍历计算
    }
    return count;
}

2. 求二叉树的深度:

递归解法:
(1)如果二叉树为空,二叉树的深度为0
(2)如果只有根节点,二叉树深度为1
(2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1

参考代码如下:

    size_t _Height(Node* pRoot)
    {
        if(pRoot == NULL)  //空树
            return 0;

        if(pRoot->_pLeft == NULL || pRoot->_pRight == NULL)
            return 1;   //只有一个根节点的树

        size_t lefthHight = _Height(pRoot->_pLeft);
        size_t rightHeight = _Height(pRoot->_pRight);

        return (lefthHight>rightHeight) ? (lefthHight+1) : (rightHeight+1);
    } 
  1. 前序遍历,中序遍历,后序遍历

前序遍历递归解法:
(1)如果二叉树为空,空操作
(2)如果二叉树不为空,访问根节点,前序遍历左子树,前序遍历右子树

参考代码如下:

void _PreOrder(Node* pRoot)
{
    if(pRoot)
    {
        cout<<pRoot->_data<<" ";
        _PreOrder(pRoot->_pLeft);
        _PreOrder(pRoot->_pRight);
    }
}

中序遍历递归解法
(1)如果二叉树为空,空操作。
(2)如果二叉树不为空,中序遍历左子树,访问根节点,中序遍历右子树

参考代码如下:

void _InOrder(Node* pRoot)
    {
        if(pRoot)
        {
            _InOrder(pRoot->_pLeft);
            cout<<pRoot->_data<<" ";
            _InOrder(pRoot->_pRight);
        }
    }

后序遍历递归解法
(1)如果二叉树为空,空操作
(2)如果二叉树不为空,后序遍历左子树,后序遍历右子树,访问根节点

参考代码如下:

void _PostOrder(Node* pRoot)
    {
        if(pRoot)
        {
            _PostOrder(pRoot->_pLeft);
            _PostOrder(pRoot->_pRight);
            cout<<pRoot->_data<<" ";
        }
    } 

4.分层遍历二叉树(按层次从上往下,从左往右):

相当于广度优先搜索,使用队列实现。队列初始化,将根节点压入队列。当队列不为空,进行如下操作:访问,若左子节点或右子节点不为空,将其压入队列,弹出一个节点。
void _LevelOrder(Node* pRoot)
    {
        if(pRoot == NULL)
            return;
        queue<Node*> q;
        q.push(pRoot);
        Node* pCur = NULL;

        while(!q.empty())
        {
            pCur = q.front();
            cout<<pCur->_data<<" ";
            if(pCur->_pLeft)
                q.push(pCur->_pLeft);
            if(pCur->_pRight)
                q.push(pCur->_pRight);
            q.pop(); 
        }
    }

5. 将二叉查找树变为有序的双向链表:

要求不能创建新节点,只调整指针。

递归解法:
(1)如果二叉树查找树为空,不需要转换,对应双向链表的第一个节点是NULL,最后一个节点是NULL2)如果二叉查找树不为空:
如果左子树为空,对应双向有序链表的第一个节点是根节点,左边不需要其他操作;

如果左子树不为空,转换左子树,二叉查找树对应双向有序链表的第一个节点就是左子树转换后双向有序链表的第一个节点,同时将根节点和左子树转换后的双向有序链 表的最后一个节点连接;

如果右子树为空,对应双向有序链表的最后一个节点是根节点,右边不需要其他操作;

如果右子树不为空,对应双向有序链表的最后一个节点就是右子树转换后双向有序链表的最后一个节点,同时将根节点和右子树转换后的双向有序链表的第一个节点连 接。

参考代码如下:

//pRoot: 二叉查找树根节点指针 
//pFirstNode: 转换后双向有序链表的第一个节点指针 
//pLastNode: 转换后双向有序链表的最后一个节点指针

void _Convert(Node* pRoot, Node*& pFirstNode, Node*& pLastNode)  
    {  
        Node* pFirstLeft = NULL;
        Node* pLastLeft = NULL;
        Node* pFirstRight = NULL;
        Node* pLastRight = NULL;

        if(pRoot == NULL)   
        {  
            pFirstNode = NULL;  
            pLastNode = NULL;  
            return;  
        }  

        if(pRoot->_pLeft == NULL)  
        {  
            // 如果左子树为空,对应双向有序链表的第一个节点是根节点  
            pFirstNode = pRoot;  
        }  
        else  
        {  
            Convert(pRoot->_pLeft, pFirstLeft, pLastLeft);  
            // 二叉查找树对应双向有序链表的第一个节点就是左子树转换后双向有序链表的第一个节点  
            pFirstNode = pFirstLeft; 

            // 将根节点和左子树转换后的双向有序链表的最后一个节点连接  
            pRoot->m_pLeft = pLastLeft;  
            pLastLeft->m_pRight = pRoot;  
        }  

        if(pRoot->_pRight == NULL)  
        {  
            // 对应双向有序链表的最后一个节点是根节点  
            pLastNode = pRoot;  
        }  
        else  
        {  
            Convert(pRoot->_pRight, pFirstRight, pLastRight);  
            // 对应双向有序链表的最后一个节点就是右子树转换后双向有序链表的最后一个节点  
            pLastNode = pLastRight;

            // 将根节点和右子树转换后的双向有序链表的第一个节点连接  
            pRoot->_pRight = pFirstRight;  
            pFirstRight->_pLeft = pRoot;  
        }  

        return;  
    }  

6. 求二叉树第K层的节点个数:

递归解法:
(1)如果二叉树为空或者k<1返回0
(2)如果二叉树不为空并且k==1,返回1
(3)如果二叉树不为空且k>1,返回左子树中k-1层的节点个数与右子树k-1层节点个数之和

参考代码如下:

size_t _GetKLevelNode(Node* pRoot, size_t k)
    {
        if(pRoot == NULL || k<1 || k>_Height(pRoot))
            return 0;

        if(k == 1)
            return 1;

        size_t left = _GetKLevelNode(pRoot->_pLeft, k-1);
        // 左子树中k-1层的节点个数
        size_t right = _GetKLevelNode(pRoot->_pRight, k-1);
        // 右子树中k-1层的节点个数  
        return left + right;
    }

7. 求二叉树中叶子节点的个数:

递归解法:
(1)如果二叉树为空,返回0
(2)如果二叉树不为空且左右子树为空,返回1
(3)如果二叉树不为空,且左右子树不同时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数
size_t _GetLeefNode(Node* pRoot)
    {
        if(pRoot == NULL) //空树
            return 0;

        if(pRoot->_pLeft == NULL && pRoot->_pRight == NULL)
            return 1;  //只有一个根节点的树

        return _GetLeefNode(pRoot->_pLeft) + _GetLeefNode(pRoot->_pRight);
    }

8. 判断两棵二叉树是否结构相同:

不考虑数据内容。结构相同意味着对应的左子树和对应的右子树都结构相同。

递归解法:
(1)如果两棵二叉树都为空,返回真
(2)如果两棵二叉树一棵为空,另一棵不为空,返回假
(3)如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假
bool StructureCmp(Node* pRoot1, Node* pRoot2)  
{  
    if(pRoot1 == NULL && pRoot2 == NULL) // 都为空,返回真  
        return true;  
    else if(pRoot1 == NULL || pRoot2 == NULL) // 有一个为空,一个不为空,返回假  
        return false; 

    bool resultLeft = StructureCmp(pRoot1->m_pLeft, pRoot2->m_pLeft); // 比较对应左子树   
    bool resultRight = StructureCmp(pRoot1->m_pRight, pRoot2->m_pRight); // 比较对应右子树  

     return (resultLeft && resultRight);  
}  

9. 判断二叉树是不是平衡二叉树:

递归解法:
(1)如果二叉树为空,返回真
(2)如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假

参考代码:

bool IsAVL(Node* pRoot, int & height)  
{  
    if(pRoot == NULL) // 空树,返回真  
    {  
        height = 0;  
        return true;  
    }  

    int heightLeft = 0;  
    bool resultLeft = IsAVL(pRoot->m_pLeft, heightLeft);  
    int heightRight = 0;  
    bool resultRight = IsAVL(pRoot->m_pRight, heightRight); 

    if(resultLeft && resultRight && abs(heightLeft - heightRight) <= 1) // 左子树和右子树都是AVL,并且高度相差不大于1,返回真  
    {  
        height = max(heightLeft, heightRight) + 1;  
        return true;  
    }  
    else  
    {  
        height = max(heightLeft, heightRight) + 1;  
        return false;  
    }  
} 

10. 求二叉树的镜像:

递归解法:
(1)如果二叉树为空,返回空
(2)如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树和右子树
void _GetBinaryMirror(Node* pRoot)
    {
        if(pRoot == NULL)
            return ;

        if(pRoot->_pLeft == NULL && pRoot->_pRight == NULL)
            return ;

        swap(pRoot->_pLeft, pRoot->_pRight);

        if(pRoot->_pLeft)
             _GetBinaryMirror(pRoot->_pLeft);

        if(pRoot->_pRight)
             _GetBinaryMirror(pRoot->_pRight);
    }
// 求二叉树的镜像:非递归:用栈实现
    void GetBinaryMirror_Nor()
    {
        queue<Node *> q;  
        Node *pointer = _pRoot;//当前处理的节点为根节点  
        while (pointer)  
        {  
            swap(pointer->_pLeft, pointer->_pRight);//交换当前处理节点的孩子  
            if (pointer->_pLeft)//左孩子不为空  
                q.push(pointer->_pLeft); 

            if (pointer->_pRight)//右孩子不为空  
                q.push(pointer->_pRight); 

            if (!q.empty())  
            {  
                pointer = q.front();  
                q.pop();  
            }  
            else  
            {  
                break;  
            }  
        } 
    }

11. 求二叉树中两个节点的最低公共祖先节点:

递归解法:
(1)如果两个节点分别在根节点的左子树和右子树,则返回根节点
(2)如果两个节点都在左子树,则递归处理左子树;如果两个节点都在右子树,则递归处理右子树
bool FindNode(Node* pRoot, Node* pNode)  
{  
    if(pRoot == NULL || pNode == NULL)  
        return false;  

    if(pRoot == pNode)  
        return true;  

    bool found = FindNode(pRoot->m_pLeft, pNode);  
    if(!found)  
        found = FindNode(pRoot->m_pRight, pNode);  

    return found;  
}  

BinaryTreeNode * GetLastCommonParent(Node* pRoot,   
 Node* pNode1, Node* pNode2)  
{  
    if(FindNode(pRoot->m_pLeft, pNode1))  
    {  
        if(FindNode(pRoot->m_pRight, pNode2))  
            return pRoot;  
        else  
            return GetLastCommonParent(pRoot->m_pLeft, pNode1, pNode2);  
    }  
    else  
    {  
        if(FindNode(pRoot->m_pLeft, pNode2))  
            return pRoot;  
        else  
            return GetLastCommonParent(pRoot->m_pRight, pNode1, pNode2);  
    }  
}  
//非递归:先求从根节点到两个节点的路径,然后再比较对应路径的节点就行,最后一个相同的节点也就是他们在二叉树中的最低公共祖先节点
bool GetNodePath(Node* pRoot, Node* pNode, list<Node*>& path)
    {
        if(pRoot == pNode)  
        {     
            path.push_back(pRoot);  
            return true;  
        }  

        if(pRoot == NULL)  
            return false;  

        path.push_back(pRoot);  
        bool found = false; 

        found = GetNodePath(pRoot->_pLeft, pNode, path);  //在左子树中寻找

        if(!found)  //在左子树中没有找到,在右子树中寻找
            found = GetNodePath(pRoot->_pRight, pNode, path); 

        if(!found)   //没有找到该节点
            path.pop_back();  
        return found;
    }

    Node* _GetLastCommonParent(Node* pRoot, Node* Node1, Node* Node2)
    {
        if(pRoot == NULL || Node1 == NULL || Node2 == NULL)
            return NULL;

        list<Node*> l1;
        bool Result1 = GetNodePath(pRoot, Node1, l1);

        list<Node*> l2;
        bool Result2 = GetNodePath(pRoot, Node2, l2);

        if(!Result1 || !Result2)
            return NULL;

        Node * pLast = NULL;  
        list<Node*>::const_iterator iter1 = l1.begin();  
        list<Node*>::const_iterator iter2 = l2.begin();

        while(iter1 != l1.end() && iter2 != l2.end())  
        {  
            if(*iter1 == *iter2)  
                pLast = *iter1;  
            else  
                break;  
            iter1++;  
            iter2++;  
        }  
        return pLast;
    }
///利用辅助空间链表,将问题转化为求两个链表的最后一个相交的节点
Node* _GetLastCommonParent3(Node* pRoot,Node* pNode1,Node* pNode2)
{
    if(pRoot==NULL || pNode1==NULL ||pNode2==NULL)
        return NULL;
    list<Node*> l1;
    list<Node*> l2;

    //从根节点开始查找节点并保存路径
    bool flags = false;
    FindNodeAndRemPath(pRoot,pNode1,l1,flags);
    flags = false;
    FindNodeAndRemPath(pRoot,pNode2,l2,flags);

    //求两个相交链表中最后一个相交的节点
    return FindCrossPointOfTwoList(l1,l2);
}

void FindNodeAndRemPath(Node* pRoot,Node* pNode,list<Node*>& l,bool& flags)//从根节点开始查找节点并保存路径
{
    if(pRoot==NULL)
        return;
    l.push_back(pRoot);
    if(pRoot == pNode)
    {
        flags = true;
        return;
    }
    if(!flags)
        FindNodeAndRemPath(pRoot->_pLeft,pNode,l,flags);
    if(!flags)
        FindNodeAndRemPath(pRoot->_pRight,pNode,l,flags);
    if(!flags)
    {
        l.pop_back();
        return;
    }
}

Node* FindCrossPointOfTwoList(list<Node*> l1,list<Node*> l2)//求两个相交链表中最后一个相交的节点
{
    //Node* pCur1 = l1.front();
    //Node* pCur2 = l2.front();
    list<Node*>::iterator it1 = l1.begin();
    list<Node*>::iterator it2 = l2.begin();
    Node* pCur = NULL;
    while(it1 != l1.end() && it2 != l2.end())
    {
        if(*it1 == *it2)
        {
            pCur = *it1;
            it1++;
            it2++;
        }
        else
        {
            return pCur;
        }
    }
    return NULL;
}

12. 求二叉树中节点的最大距离:

即二叉树中相距最远的两个节点之间的距离。

递归解法:
(1)如果二叉树为空,返回0,同时记录左子树和右子树的深度,都为0
(2)如果二叉树不为空,最大距离要么是左子树中的最大距离,要么是右子树中的最大距离,要么是左子树节点中到根节点的最大距离+右子树节点中到根节点的最大距离,同时记录左子树和右子树节点中到根节点的最大距离。

参考代码如下:

int GetMaxDistance(Node * pRoot, int & maxLeft, int & maxRight)  
{  
    // maxLeft, 左子树中的节点距离根节点的最远距离  
    // maxRight, 右子树中的节点距离根节点的最远距离  
    if(pRoot == NULL)  
    {  
        maxLeft = 0;  
        maxRight = 0;  
        return 0;  
    }  
    int maxLL, maxLR, maxRL, maxRR;  
    int maxDistLeft, maxDistRight;  
    if(pRoot->m_pLeft != NULL)  
    {  
        maxDistLeft = GetMaxDistance(pRoot->m_pLeft, maxLL, maxLR);  
        maxLeft = max(maxLL, maxLR) + 1;  
    }  
    else  
    {  
        maxDistLeft = 0;  
        maxLeft = 0;  
    }  
    if(pRoot->m_pRight != NULL)  
    {  
        maxDistRight = GetMaxDistance(pRoot->m_pRight, maxRL, maxRR);  
        maxRight = max(maxRL, maxRR) + 1;  
    }  
    else  
    {  
        maxDistRight = 0;  
        maxRight = 0;  
    }  
    return max(max(maxDistLeft, maxDistRight), maxLeft+maxRight);  
}  

13. 由前序遍历序列和中序遍历序列重建二叉树:

二叉树前序遍历序列中,第一个元素总是树的根节点的值。中序遍历序列中,左子树的节点的值位于根节点的值的左边,右子树的节点的值位于根节点的值的右边。

递归解法:
(1)如果前序遍历为空或中序遍历为空或节点个数小于等于0,返回NULL。
(2)创建根节点。前序遍历的第一个数据就是根节点的数据,在中序遍历中找到根节点的位置,可分别得知左子树和右子树的前序和中序遍
历序列,重建左右子树。
BinaryTreeNode * RebuildBinaryTree(int* pPreOrder, int* pInOrder, int nodeNum)  
{  
    if(pPreOrder == NULL || pInOrder == NULL || nodeNum <= 0)  
        return NULL;  
    BinaryTreeNode * pRoot = new BinaryTreeNode;  
    // 前序遍历的第一个数据就是根节点数据  
    pRoot->_data = pPreOrder[0];  
    pRoot->_pLeft = NULL;  
    pRoot->_pRight = NULL; 

    // 查找根节点在中序遍历中的位置,中序遍历中,根节点左边为左子树,右边为右子树  
    int rootPositionInOrder = -1;  
    for(int i = 0; i < nodeNum; i++)  
        if(pInOrder[i] == pRoot->m_nValue)  
        {  
            rootPositionInOrder = i;  
            break;  
}

    if(rootPositionInOrder == -1)  
    {  
        throw std::exception("Invalid input.");  
    }  
    // 重建左子树  
    int nodeNumLeft = rootPositionInOrder;  
    int * pPreOrderLeft = pPreOrder + 1;  
    int * pInOrderLeft = pInOrder;  
    pRoot->m_pLeft = RebuildBinaryTree(pPreOrderLeft, pInOrderLeft, nodeNumLeft); 

    // 重建右子树  
    int nodeNumRight = nodeNum - nodeNumLeft - 1;  
    int * pPreOrderRight = pPreOrder + 1 + nodeNumLeft;  
    int * pInOrderRight = pInOrder + nodeNumLeft + 1;  
    pRoot->m_pRight = RebuildBinaryTree(pPreOrderRight, pInOrderRight, nodeNumRight);  
    return pRoot;  
}

14.判断二叉树是不是完全二叉树:

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全
二叉树。
有如下算法,按层次(从上到下,从左到右)遍历二叉树,当遇到一个节点的左子树为空时,则该节点右子树必须为空,且后面遍历的节点左
右子树都必须为空,否则不是完全二叉树。
bool IsCompleteBinaryTree()
    {
        if(_pRoot == NULL)
            return true;
        Node* pCur = NULL;
        queue<Node*> q;
        q.push(_pRoot);

        int flag = false;

        while(!q.empty())
        {
            pCur = q.front();
            q.pop();

            if(flag == true)
            {
                if(pCur->_pLeft != NULL || pCur->_pRight != NULL)
                    return false;
                return true;
            }
            else
            {
                if(pCur->_pLeft != NULL && pCur->_pRight != NULL)  
                {  
                    q.push(pCur->_pLeft);  
                    q.push(pCur->_pRight);  
                }
                else if(pCur->_pRight!= NULL)
                {
                    return false;
                }
                else if(pCur->_pLeft != NULL)
                {
                    q.push(pCur->_pLeft);
                    flag = true;
                }
                else
                {
                    flag = true;
                }
            }
        }
        return false;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
(1)非递归定义 树(tree)是由n(n≥0)个结点组成的有限集合。n=0的树称为空树;n>0的树T: ① 有且仅有一个结点n0,它没有前驱结点,只有后继结点。n0称作树的根(root)结点。 ② 除结点外n0 , 其余的每一个结点都有且仅有一个直接前驱结点;有零个或多个直接后继结点。 (2)递归定义 一颗大树分成几个大的分枝,每个大分枝再分成几个小分枝,小分枝再分成更小的分枝,… ,每个分枝也都是一颗树,由此我们可以给出树的递归定义。 树(tree)是由n(n≥0)个结点组成的有限集合。n=0的树称为空树;n>0的树T: ① 有且仅有一个结点n0,它没有前驱结点,只有后继结点。n0称作树的根(root)结点。 ② 除根结点之外的其他结点分为m(m≥0)个互不相交的集合T0,T1,…,Tm-1,其中每个集合Ti(0≤i<m)本身又是一棵树,称为根的子树(subtree)。 2、掌握树的各种术语: (1) 父母、孩子与兄弟结点 (2) 度 (3) 结点层次、树的高度 (4) 边、路径 (5) 无序树、有序树 (6) 森林 3、二叉树的定义 二叉树(binary tree)是由n(n≥0)个结点组成的有限集合,此集合或者为空,或者由一个根结点加上两棵分别称为左、右子树的,互不相交的二叉树组成。 二叉树可以为空集,因此根可以有空的左子树或者右子树,亦或者左、右子树皆为空。 4、掌握二叉树的五个性质 5、二叉树的二叉链表存储。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值