二叉樹相關面試題彙總

說明:本博文在以前面試題的基礎上彙總了二叉樹的常見面試題。

二叉樹中封裝的功能有:

    void _CreateTree(Node*& pRoot, const T array[], size_t size, size_t& index, const T& invalid);//創建樹

    Node* _CopyBinaryTree(Node* pRoot);//拷貝樹

    void _Destroy(Node*& pRoot);//清空樹

    void _PreOrder(Node* pRoot);//遞歸實現:先序遍歷
    void _PreOrder_nor(Node* pRoot);//非遞歸實現:先序遍歷

    void _InOrder(Node* pRoot);//遞歸實現:中序遍歷
    void _InOrder_nor(Node* pRoot);//非遞歸實現:中序遍歷

    void _PostOrder(Node* pRoot);//遞歸實現:後序遍歷
    void _PostOrder_nor(Node* pRoot);//非遞歸實現:後序遍歷

    void _LevelOrder(Node* pRoot);//層序遍歷

    Node* _GetParent(Node* pRoot, Node* x);//獲取雙親結點

    Node* _Find(Node* pRoot, const T& value);//找到值爲value的結點

    size_t _Height(Node* pRoot);//獲取樹的高度

    size_t _GetLeefNode(Node* pRoot);//獲取葉子結點個數

    size_t _GetKLevelNode(Node* pRoot, size_t k);//獲取某一層結點個數

    void _GetBinaryMirror(Node* pRoot);//遞歸實現:將二叉樹變爲其鏡像

    bool IsCompleteBinaryTree();//判斷二叉樹樹是否是完全二叉樹

二叉樹信息:

#include <iostream>
#include <queue>
#include <stack>
using namespace std;

template <typename T>
struct BinaryTreeNode  //節點信息
{
    BinaryTreeNode(const T& data)
        :m_data(data)
        , m_pLeft(nullptr)
        , m_pRight(nullptr)
    {}
    T m_data;
    BinaryTreeNode<T>* m_pLeft;//左孩子
    BinaryTreeNode<T>* m_pRight;//右孩子
};


template <typename T>
class BinaryTree//將成員函數的實現進行簡單的封裝,用戶只能顯示調用公共接口
{
    typedef BinaryTreeNode<T> Node;
public:
    BinaryTree()//無參構造函數
        :m_pRoot(nullptr)
    {}
    BinaryTree(const T array[], size_t size, const T& invalid)//帶參構造函數
    {
        size_t index = 0;
        _CreateTree(m_pRoot, array, size, index, invalid);
    }
    BinaryTree(const BinaryTree<T>& t)//拷貝構造函數
    {
        m_pRoot = _CopyBinaryTree(t.m_pRoot);
    }
    BinaryTree<T>& operator=(const BinaryTree<T>& t)//賦值運算符重載成員函數
    {
        _Destroy(m_pRoot);
        m_pRoot = _CopyBinaryTree(t.m_pRoot);
        return *this;
    }
    ~BinaryTree()//析構函數
    {
        _Destroy(m_pRoot);
    }
    void PreOrder()//前序遍歷
    {
        cout << "前序遍歷:";
        _PreOrder_nor(m_pRoot);
        cout << endl;
    }
    void InOrder()//中序遍歷
    {
        cout << "中序遍歷";
        _InOrder_nor(m_pRoot);
        cout << endl;
    }
    void PostOrder()//後序遍歷
    {
        cout << "後序遍歷";
        _PostOrder_nor(m_pRoot);
        cout << endl;
    }
    void LevelOrder()//層序遍歷
    {
        cout << "層序遍歷";
        _LevelOrder(m_pRoot);
        cout << endl;
    }

    Node* GetParent(Node* x)//獲取該結點的雙親結點
    {
        return _GetParent(m_pRoot, x);
    }

    Node* Find(const T& value)//找到值爲value的結點
    {
        return _Find(m_pRoot, value);
    }

    Node* GetLeftChild(Node* pCur)//返回左孩子
    {
        if (pCur)
            return pCur->m_pLeft;
        return nullptr;
    }

    Node* GetRightChild(Node* pCur)//返回右孩子
    {
        if (pCur)
            return pCur->m_pRight;
        return nullptr;
    }

    size_t Height()//獲取樹的高度
    {
        return _Height(m_pRoot);
    }

    size_t GetLeefNode()//獲取葉子結點個數
    {
        return _GetLeefNode(m_pRoot);
    }

    size_t GetKLevelNode(size_t k)//獲取某一層結點個數
    {
        return _GetKLevelNode(m_pRoot, k);
    }

    void GetBinaryMirror_Nor();// 將二叉樹變爲其鏡像:非遞歸

    void GetBinaryMirror()  // 將二叉樹變爲其鏡像:遞歸版本
    {
        _GetBinaryMirror(m_pRoot);
    }

    // 利用層序遍歷來處理--> 關鍵:找第一個度不爲2的結點-->後續結點
    // 如果有孩子則不是完全二叉樹
    // 否則:是
    bool IsCompleteBinaryTree();

private:
    void _CreateTree(Node*& pRoot, const T array[], size_t size, size_t& index, const T& invalid);//創建樹

    Node* _CopyBinaryTree(Node* pRoot);//拷貝樹

    void _Destroy(Node*& pRoot);//清空樹

    void _PreOrder(Node* pRoot);//遞歸實現:先序遍歷
    void _PreOrder_nor(Node* pRoot);//非遞歸實現:先序遍歷

    void _InOrder(Node* pRoot);//遞歸實現:中序遍歷
    void _InOrder_nor(Node* pRoot);//非遞歸實現:中序遍歷

    void _PostOrder(Node* pRoot);//遞歸實現:後序遍歷
    void _PostOrder_nor(Node* pRoot);//非遞歸實現:後序遍歷

    void _LevelOrder(Node* pRoot);//層序遍歷

    Node* _GetParent(Node* pRoot, Node* x);//獲取雙親結點

    Node* _Find(Node* pRoot, const T& value);//找到值爲value的結點

    size_t _Height(Node* pRoot);//獲取樹的高度

    size_t _GetLeefNode(Node* pRoot);//獲取葉子結點個數

    size_t _GetKLevelNode(Node* pRoot, size_t k);//獲取某一層結點個數

    void _GetBinaryMirror(Node* pRoot);//遞歸實現:將二叉樹變爲其鏡像

private:
    BinaryTreeNode<T>* m_pRoot;//根結點
};

利用數組,創建一個二叉樹(比如“124#7#35##68#”表示先序遍歷順序,#用來表示空結點)
這裏寫圖片描述

template <typename T>//創建樹
void BinaryTree<T>::_CreateTree(Node*& pRoot, const T array[], size_t size, size_t& index, const T& invalid)
{
    if ((index < size) && (array[index] != invalid))
    {
        pRoot = new Node(array[index]);
        _CreateTree(pRoot->m_pLeft, array, size, ++index, invalid);
        _CreateTree(pRoot->m_pRight, array, size, ++index, invalid);
    }
}

拷貝一個二叉樹

template <typename T>//拷貝樹
BinaryTreeNode<T>* BinaryTree<T>::_CopyBinaryTree(Node* pRoot)
{
    Node* pNewRoot = nullptr;
    if (pRoot)
    {
        pNewRoot = new Node(pRoot->m_data);
        pNewRoot->m_pLeft = _CopyBinaryTree(pRoot->m_pLeft);
        pNewRoot->m_pRight = _CopyBinaryTree(pRoot->m_pRight);
    }
    return pNewRoot;
}

銷燬一顆二叉樹

template <typename T>
void BinaryTree<T>::_Destroy(Node*& pRoot)//清空樹
{
    if (pRoot)
    {
        _Destroy(pRoot->m_pLeft);
        _Destroy(pRoot->m_pRight);
        delete pRoot;
        pRoot = nullptr;
    }
}

遞歸實現前序遍歷

template <typename T>
void BinaryTree<T>::_PreOrder(Node* pRoot)//遞歸實現:前序遍歷
{
    if (pRoot)
    {
        cout << pRoot->m_data << " ";
        _PreOrder(pRoot->m_pLeft);
        _PreOrder(pRoot->m_pRight);
    }
}

非遞歸實現前序遍歷

template <typename T>
void BinaryTree<T>::_PreOrder_nor(Node* pRoot)//非遞歸實現:前序遍歷(利用棧實現)
{
    if (nullptr == pRoot)//空樹
        return;
    stack<Node*> s;
    s.push(pRoot);

    while (!s.empty())
    {
        Node* pTemp = s.top();
        cout << pTemp->m_data << " ";
        s.pop();//pop放在push之前,由於棧的LIFO特性

        if (pTemp->m_pRight)
            s.push(pTemp->m_pRight);//一定要先存入右子樹,後存入左子樹,因爲棧的LIFO特性
        if (pTemp->m_pLeft)
            s.push(pTemp->m_pLeft);
    }
}

遞歸實現中序遍歷

template <typename T>
void BinaryTree<T>::_InOrder(Node* pRoot)//遞歸實現:中序遍歷
{
    if (pRoot)
    {
        _InOrder(pRoot->m_pLeft);
        cout << pRoot->m_data << " ";
        _InOrder(pRoot->m_pRight);
    }
}

非遞歸實現中序遍歷

template <typename T>
void BinaryTree<T>::_InOrder_nor(Node* pRoot)//非遞歸實現:中序遍歷(1,找到樹最左邊的節點,並保存路徑 2,訪問當前節點,並將其右子樹作爲根節點。3,重複12)
{
    if (nullptr == pRoot)
        return;

    stack<Node*> s;
    Node* pCur = pRoot;

    while (!s.empty() || pCur)//注意:後面的條件,因爲是中序遍歷,當根節點出棧之後,還需遍歷右子樹
    {
        while (pCur)//找到最左邊的葉子結點,將路徑上結點入棧
        {
            s.push(pCur);
            pCur = pCur->m_pLeft;
        }

        Node* pTemp = s.top();
        cout << pTemp->m_data << " ";
        s.pop();

        pCur = pTemp->m_pRight;//進入右子樹
    }
}

遞歸實現後序遍歷

template <typename T>
void BinaryTree<T>::_PostOrder(Node* pRoot)//後序遍歷
{
    if (pRoot)
    {
        _PostOrder(pRoot->m_pLeft);
        _PostOrder(pRoot->m_pRight);
        cout << pRoot->m_data << " ";
    }
}

非遞歸實現後序遍歷

template <typename T>
void BinaryTree<T>::_PostOrder_nor(Node* pRoot)//非遞歸實現:後序遍歷
{/*順序:總是:左-->右-->根
 1,找到最左邊的葉子結點
 2,訪問最左邊的結點(即棧頂結點)
 3,若棧頂元素的兄弟結點爲空,或者不爲空但已經訪問過了(不爲空時,採用標記避免陷入死循環(一直保存右孩子)中),就輸出棧頂結點值
 4,否則,進入棧頂元素的兄弟結點所在樹中,進行1-2步迭代
 */
    if (nullptr == pRoot)//空樹
        return;
    stack<Node*> s;
    Node* pCur = pRoot;//當前樹的根結點
    Node* pPre = nullptr;//最近一次訪問的節點
    while (!s.empty() || pCur)//樹不爲空,或者樹爲空時根結點不爲空(即還沒有將節點放入棧中)
    {
        while (pCur)//找到該樹最左邊的葉子結點,並且保存路徑
        {
            s.push(pCur);
            pCur = pCur->m_pLeft;//一直找左孩子。
        }

        pCur = s.top();//取出棧頂元素

        if ((nullptr == pCur->m_pRight) || (pPre == pCur->m_pRight))//若棧頂元素的右孩子爲空,或者棧頂元素的右孩子孩子不爲空並且等於剛纔輸出的節點(避免陷入死循環:一直保存右孩子)
        {
            cout << pCur->m_data << " ";//打印根結點的前提是:右子樹爲空
            s.pop();
            pPre = pCur;
            pCur = nullptr;//置爲空,使得下次迭代中,跳出尋找那次迭代中的樹的最左邊結點,避免陷入死循環(一直保存左孩子)
        }
        else
            pCur = pCur->m_pRight;
    }
}

實現層序遍歷

template <typename T>
void BinaryTree<T>::_LevelOrder(Node* pRoot)//層序遍歷(利用隊列實現)
{   /*
    畫圖理解:
    1,先將根節點入隊列,
    2,進入循環,隊首結點出隊列,輸出其值。
    3,將其左右孩子節點入隊列,
    4,進行2-3循環,直到隊列爲空
    */
    if (!pRoot)
        return;

    queue<Node*> q;
    q.push(pRoot);

    while (!q.empty())
    {
        Node* pCur = q.front();
        q.pop();
        cout << pCur->m_data << " ";

        if (pCur->m_pLeft)
            q.push(pCur->m_pLeft);

        if (pCur->m_pRight)
            q.push(pCur->m_pRight);
    }
}

遞歸實現獲取雙親結點

template <typename T>
BinaryTreeNode<T>* BinaryTree<T>::_GetParent(Node* pRoot, Node* x)//遞歸實現:獲取雙親結點
{
    if ((nullptr == pRoot) || (pRoot == x))//空樹或者該結點是根結點,沒有雙親結點,返回nullptr
        return nullptr;

    if ((pRoot->m_pLeft == x) || (pRoot->m_pRight == x))//若x是根結點個左右孩子,返回該根結點
        return pRoot;
    //以上兩條判斷語句是遞歸出口

    //利用遞歸,將問題變爲在左右子樹中尋找x的雙親結點
    Node* parent = nullptr;
    if (parent = _GetParent(pRoot->m_pLeft, x))//左子樹中尋找
        return parent;
    if (parent = _GetParent(pRoot->m_pRight, x))//右子樹中尋找
        return parent;

    return nullptr;//在左右子樹中未找到x的雙親結點。
}

遞歸實現查找某一值

template <typename T>
BinaryTreeNode<T>* BinaryTree<T>::_Find(Node* pRoot, const T& value)//遞歸實現:找到值爲value的結點
{
    if (nullptr == pRoot)//空樹或空結點
        return nullptr;

    if (pRoot->m_data == value)
        return pRoot;
    //以上兩條語句是遞歸出口

    //利用遞歸,將問題變爲在左右子樹中尋找值爲value的節點
    Node* p_node = nullptr;
    if (p_node = _Find(pRoot->m_pLeft, value))//左子樹中尋找
        return p_node;
    if (p_node = _Find(pRoot->m_pRight, value))//右子樹中尋找
        return p_node;

    return nullptr;//在左右子樹中未找到值爲value的結點。
}

遞歸實現獲取樹的高度

template <typename T>
size_t BinaryTree<T>::_Height(Node* pRoot)//遞歸實現:獲取樹的高度
{
    if (nullptr == pRoot)//空樹或空節點
        return 0;
    //以上語句爲遞歸出口

    //利用遞歸:獲取左右子樹的高度,並將左右子樹的高度中較大的高度加1,返回(當前樹的pRoot不爲空,所以加1)
    size_t h_left = _Height(pRoot->m_pLeft);//獲得左子樹高度
    size_t h_right = _Height(pRoot->m_pRight);//獲得右子樹高度

    return h_left > h_right ? h_left + 1 : h_right + 1;//返回樹的高度
}

遞歸實現獲取葉子結點個數

template <typename T>
size_t BinaryTree<T>::_GetLeefNode(Node* pRoot)//遞歸實現:獲取葉子結點個數
{
    if (nullptr == pRoot)//空樹或空結點
        return 0;
    if ((nullptr == pRoot->m_pLeft) && (nullptr == pRoot->m_pRight))//當前節點時葉子結點
        return 1;
    //以上兩個判斷語句是遞歸出口

    //利用遞歸,獲取左右子樹的葉子結點個數
    size_t leaves_left = _GetLeefNode(pRoot->m_pLeft);//獲取左子樹葉子結點數
    size_t leaves_right = _GetLeefNode(pRoot->m_pRight);//獲取右子樹葉子結點數

    return leaves_left + leaves_right;//返回左右子樹葉子結點之和
}

遞歸實現獲取第K層節點個數

template <typename T>
size_t BinaryTree<T>::_GetKLevelNode(Node* pRoot, size_t k)//遞歸實現:獲取某一層結點個數
{
    if ((nullptr == pRoot) || (1 > k))//空樹或空結點、k小於1
        return 0;
    if (1 == k)//k=1表示當前結點
        return 1;
    //以上兩個判斷語句是遞歸出口

    //利用遞歸,獲取左右子樹中處於k-1層結點個數
    size_t nodes_left = _GetKLevelNode(pRoot->m_pLeft, k - 1);//獲取左子樹中k-1層結點個數
    size_t nodes_right = _GetKLevelNode(pRoot->m_pRight, k - 1);//獲取右子樹中k-1層結點個數

    return nodes_left + nodes_right;
}

非遞歸獲取二叉樹的鏡像

template <typename T>
void BinaryTree<T>::GetBinaryMirror_Nor()// 將二叉樹變爲其鏡像:非遞歸
{
    if (nullptr == m_pRoot)//空樹
        return;

    //利用層序遍歷的思想,將每一層中,每一個節點按順序放入隊列中,然後改變其左右孩子的位置
    queue<Node*> q;
    Node* pCur = m_pRoot;
    q.push(pCur);
    while (!q.empty())
    {
        pCur = q.front();
        q.pop();//由於是隊列,對於出隊列和入隊列的順序沒有要求

        if (pCur->m_pLeft || pCur->m_pRight)//如果當前節點不是葉子結點(即:有左右孩子)
        {
            std::swap(pCur->m_pLeft, pCur->m_pRight);//交換左右孩子

                                                     //將存在的左孩子或右孩子入隊列
            if (pCur->m_pLeft)
                q.push(pCur->m_pLeft);
            if (pCur->m_pRight)
                q.push(pCur->m_pRight);
        }
    }
}

遞歸獲取二叉樹的鏡像

template <typename T>
void BinaryTree<T>::_GetBinaryMirror(Node* pRoot)   // 將二叉樹變爲其鏡像:遞歸版本
{
    if (nullptr == pRoot)//空樹
        return;

    if ((nullptr == pRoot->m_pLeft) && (nullptr == pRoot->m_pRight))//如果_是葉子結點
        return;
    //以上兩條判斷語句是遞歸出口

    std::swap(pRoot->m_pLeft, pRoot->m_pRight);//交換左右孩子

    //將存在的左右孩子樹,作爲新樹,執行相同功能
    //if (pRoot->m_pLeft),不加判空語句,在下次遞歸中會判空。
    _GetBinaryMirror(pRoot->m_pLeft);
    //if (pRoot->m_pRight),不加判空語句,在下次遞歸中會判空。
    _GetBinaryMirror(pRoot->m_pRight);
}

判斷一個樹是否是完全二叉樹

/*
由於完全二叉樹的規律,可以使用以下解法。
1,利用層序遍歷的順序找到第一個度爲0或度爲1(只有左子樹,沒有右子樹)的結點。(關鍵)
2,若在層序遍歷中,該結點之後存在某個結點擁有孩子,則不是完全二叉樹。
3,否則,是完全二叉樹。
*/
template <typename T>
bool BinaryTree<T>::IsCompleteBinaryTree()
{
    if (nullptr == m_pRoot)
        return true;//空樹也是完全二叉樹

    queue<Node*> q;
    Node* pCur = m_pRoot;
    q.push(pCur);
    bool flag = false;//flag表示是否找到了滿足條件的節點

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

        if (!flag && pCur->m_pLeft && pCur->m_pRight)//flag=false且該結點度爲2
        {
            q.push(pCur->m_pLeft);
            q.push(pCur->m_pRight);
        }
        else if ((nullptr == pCur->m_pLeft) && pCur->m_pRight)//該結點只有右子樹,沒有左子樹,不是完全二叉樹
        {
            return false;
        }
        else if (flag && (pCur->m_pLeft || pCur->m_pRight))//flag=true且該結點有孩子節點,不是完全二叉樹
        {
            return false;
        }
        else//剩餘情況,度爲0,度位1(只有左子樹,沒有右子樹,另外一種情況在if_2中排除)。(度爲2情況在if_1, if_3中已經排除)。
            flag = true;//找到滿足條件的結點,並且標記已找到。
    }

    return true;//遍歷二叉樹過程中,沒有提前退出,表示是一個完全二叉樹
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章