二叉樹的經典面試題總結

1、二叉樹的構造

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

template <class T>
struct TreeNode
{
    TreeNode(const T& value = T())
        :_value(value)
        ,_lchild(0)
        ,_rchild(0)
    {}
    T _value;//節點的值
    TreeNode<T>* _lchild;//左孩子
    TreeNode<T>* _rchild;//右孩子
};

template <class T>
class BinaryTree
{
public:
    typedef TreeNode<T> Node;
    BinaryTree()//無參構造函數
        :_root(NULL)
    {}
    BinaryTree(const T* a,size_t size,const T& invalid)//構造函數
    {
        assert(a);
        size_t index = 0;
        _root = CreatTree(a,size,invalid,index);
    }
    BinaryTree(const BinaryTree<T>& b)//拷貝構造
    {
        _root = Copy(b._root);
    }
    //現代寫法的賦值運算符重載1
    BinaryTree& operator=(const BinaryTree<T>& b)
    {
        if (this != &b)
        {
            Node* tmp = Copy(b._root);
            Destroy(_root) ;
            _root = tmp;
            //swap(_root,tmp);
        }
        return *this;
    }
    ////現代寫法的賦值運算符重載2
    //BinaryTree& operator=(BinaryTree<T> b)
    //{
    //  swap(b._root,_root);
    //  return *this;
    //}
    ~BinaryTree()//析構
    {
        if (NULL != _root)
        {
            Destroy(_root);
            _root = NULL;
        }
    }
    protected:
    //按照先序遍歷遞歸建樹
    Node* CreatTree(const T* a,size_t size,const T& invalid,size_t& index)//注意index的傳值方式
    {
        assert(a);
        Node* root = NULL;
        if (a[index] != invalid && index < size)//按先序遍歷建樹
        {
            root = new Node(a[index]);
            root->_lchild = CreatTree(a,size,invalid,++index);
            root->_rchild = CreatTree(a,size,invalid,++index);
        }
        return root;
    }
    //拷貝對象
    Node* Copy(Node* root)
    {
        Node* tmp = NULL;

        if(root)
        {
            tmp = new Node(root->_value);
            tmp->_lchild = Copy(root->_lchild);
            tmp->_rchild = Copy(root->_rchild);
        }
        return tmp;
    }
    //釋放空間
    void Destroy(Node*& root)
    {
        if(root)//用後序遍歷方式釋放空間
        {
            Destroy(root->_rchild);
            Destroy(root->_lchild);
            delete root;
            root = NULL;
        }
    }
private:
    Node* _root;//根節點
};

2、遍歷二叉樹
二叉樹的遍歷方式一共分爲四種:先序遍歷、中序遍歷、後序遍歷和層序遍歷,而前三種遍歷又分爲遞歸遍歷和非遞歸遍歷,層序遍歷則是藉助隊列先進先出的特性來輔助完成。
【遞歸遍歷二叉樹】

 void preorder(Node* root)//先序遍歷打印樹的各個節點  
    {  
        if (root)  
        {  
            cout<<root->_value<<" ";  //訪問當前節點的值
            preorder(root->_lchild); //遞歸遍歷左子樹 
            preorder(root->_rchild); //遞歸遍歷右子樹 
        }  
    }  
    void inorder(Node* root)//中序遍歷打印樹的各個節點  
    {  
        if (root)  
        {  
            preorder(root->_lchild);  //遞歸遍歷左子樹 
            cout<<root->_value<<" ";  //訪問當前節點的值
            preorder(root->_rchild);  //遞歸遍歷右子樹 
        }  
    }  
    void postorder(Node* root)//後序遍歷打印樹的各個節點  
    {  
        if (root)  
        {  
            preorder(root->_lchild);  //遞歸遍歷左子樹 
            preorder(root->_rchild);  //遞歸遍歷右子樹 
            cout<<root->_value<<" ";  //訪問當前節點的值
        }  
    }  
    void levelorder(Node* root)//層序遍歷打印樹的各個節點  
    {  
        queue<Node*> q;  
        if (root)  
        {  
            q.push(root);//將根節點插進隊列  
        }  
        while(!q.empty())  
        {  
            Node* front = q.front();  
            q.pop();  
            cout<<front->_value<<" ";  
            if (front->_lchild)  
            {  
                q.push(front->_lchild);  
            }  
            if (front->_rchild)  
            {  
                q.push(front->_rchild);  
            }  
        }  
    }  

非遞歸遍歷二叉樹
提供給外部的接口:

public:
    void PreOrder()//先序遍歷打印樹的各個節點
    {
        cout<<"先序遍歷:";
        preorderR(_root);
        cout<<endl;
    }
    void InOrder()//中序遍歷打印樹的各個節點
    {
        cout<<"中序遍歷:";
        inorderR(_root);
        cout<<endl;
    }
    void PostOrder()//後序遍歷打印樹的各個節點
    {
        cout<<"後序遍歷:";
        postorderR(_root);
        cout<<endl;
    }
    void LevelOrder()//層序遍歷打印樹的各個節點
    {
        cout<<"層序遍歷:";
        levelorder(_root);
        cout<<endl;
    }

算法實現:

void preorderR(Node* root)//先序遍歷打印樹的各個節點
    {
        Node* cur = root;
        stack<Node*> s;
        while (!s.empty() || cur)//只要當前節點和棧不同時爲空,就說明樹沒遍歷完
        {
            while(cur)//先序遍歷,遇到樹根節點直接訪問數據並將其壓棧
            {
                cout<<cur->_value<<" ";
                s.push(cur);
                cur = cur->_lchild;
            }

            Node* top = s.top();//取出棧頂元素,到此說明此節點以及其左子樹已經訪問過了
            s.pop();

            cur = top->_rchild;//以子問題的方式去訪問右子樹
        }
        cout<<endl;
    }
    void inorderR(Node* root)//中序遍歷打印樹的各個節點
    {
        Node* cur = root;
        stack<Node*> s;
        while(!s.empty() || cur)//只要當前節點和棧不同時爲空,就說明樹沒遍歷完
        {
            while(cur)//中序遍歷,遇到樹根節點直接將其壓棧
            {
                s.push(cur);
                cur = cur->_lchild;
            }

            Node* top = s.top();//取出棧頂元素,到此說明此節點的左子樹已經訪問過了
            cout<<top->_value<<" ";//訪問棧頂元素(即根節點)
            s.pop();

            cur = top->_rchild;//以子問題的方式去訪問右子樹
        }
        cout<<endl;
    }
    void postorderR(Node* root)//後序遍歷打印樹的各個節點
    {
        Node* cur = root;
        Node* prev = NULL;//上一個訪問過的數據
        stack<Node*> s;
        while(!s.empty() || cur)//只要當前節點和棧不同時爲空,就說明樹沒遍歷完
        {
            //後序遍歷,遇到樹根節點直接將其壓棧
            while(cur)
            {
                s.push(cur);
                cur = cur->_lchild;
            }

            Node* top = s.top();//取棧頂元素,但不一定能訪問

            //當節點右子樹爲空或已經訪問過時對其直接進行訪問
            if (top->_rchild==NULL || top->_rchild==prev)
            {
                cout<<top->_value<<" ";
                prev = top;//將prev更新爲已經訪問的節點
                s.pop();
            }
            else//以子問題的方式去訪問右子樹
            {
                cur = top->_rchild;
            }
       }
        cout<<endl;
    }

    void levelorder(Node* root)//層序遍歷打印樹的各個節點
    {
        queue<Node*> q;
        if (root)
        {
            q.push(root);//將根節點插進隊列
        }
        while(!q.empty())
        {
            Node* front = q.front();
            q.pop();
            cout<<front->_value<<" ";
            if (front->_lchild)
            {
                q.push(front->_lchild);
            }
            if (front->_rchild)
            {
                q.push(front->_rchild);
            }
        }
    }

3、求二叉樹的高度(深度)
外部接口:

size_t Depth()//求樹的深度
    {
        cout<<"depth:";
        return depth(_root);
    }

算法實現:

size_t depth(Node* root)//求樹的深度
    {
        if (NULL == root)
        {
            return 0;
        }
        else
        {
            return depth(root->_lchild)>depth(root->_rchild)?
                (depth(root->_lchild)+1):(depth(root->_rchild)+1);
        }
    }

4、求二叉樹中節點的個數
外部接口:

size_t Size()//求樹中的節點個數
    {
        cout<<"size:";
        return size(_root);
    }

算法實現:

size_t size(Node* root)//求樹中的節點個數
    {
        size_t count = 0;
        if (NULL == root)
        {
            count = 0;
        }
        else
        {
            //當前節點 = 左子樹節點 + 右子樹節點 + 1
            count = size(root->_lchild) + size(root->_rchild)+ 1;
        }
        return count;
    }

5、求二叉樹中葉子節點的個數
外部接口:

size_t GetLeafSize()
    {
        cout<<"leaf_size:";
        return getleafsize(_root);
    }

算法實現:

size_t getleafsize(Node* root)//求葉節點的個數
    {
        if (NULL == root)//空樹
        {
            return 0;
        }
        if (NULL == root->_lchild && NULL == root->_rchild)//左葉節點右節點均爲空,即
        {
            return 1;
        }
        else//左子樹的葉節點+右子樹的葉節點
        {
            return getleafsize(root->_lchild)+getleafsize(root->_rchild);
        }
    }

6、求二叉樹第K層的節點個數(假設根節點爲第1層)
外部接口:

    size_t GetKLevelSize(size_t k)//樹中第k層的節點個數
    {
        cout<<k<<"_level_size:";
        return getklevelsize(_root,k);
    }

算法實現:

size_t getklevelsize(Node* root,size_t k)//樹中第k層的節點個數
    {
        assert(k>0);

        size_t count = 0;
        if (NULL == root)
        {
            return 0;
        }
        if (k == 1)
        {
            count++;
        }
        else
        {
            count =  getklevelsize(root->_lchild,k-1)
                + getklevelsize(root->_rchild,k-1);
        }
        return count;
    }

7、判斷一個節點是否在二叉樹中
外部接口:

    bool Find(const T& data)      //判斷一個值爲的節點是否在當前二叉樹中
    {
        return _Find(_root,data);
    }

算法實現:

bool _Find(Node* root,const T& data)    //查找指定數據的節點
    {
        if (NULL == root)
        {
            return false;
        }
        else if (root->_data == data)
        {
            return true;
        }
        else
        {
            bool isIn = false;
            if (root->_left && !isIn)
                isIn = _Find(root->_left,data);
            if (root->_right && !isIn)
                isIn = _Find(root->_right,data);
            return isIn;
        }
    }

8、求兩個節點的最近公共祖先
外部接口:

Node* sameAncester(Node* n1,Node* n2)      //求兩個節點的最近公共祖先
    {
        //return _sameAncester1(_root,n1,n2);
        //return _sameAncester2(_root,n1,n2);
        return _sameAncester3(_root,n1,n2);
    }

算法實現:

Node* _sameAncester1(Node*& root,Node* n1,Node* n2)//求兩個節點的最近公共祖先(時間複雜度:O(N^2))
    {
        assert(n1 && n2);
        //保證兩個節點都在當前樹中,且當前樹不爲空
        if (root && Find(n1->_data) && Find(n2->_data))
        {
            //其中一個節點爲根節點,直接返回根節點
            if (root->_data == n1->_data || root->_data == n2->_data)
            {
                return root;
            }
            //兩個節點:1、都在左子樹 2、都在右子樹 3、一個在左子樹,一個在右子樹
            else
            {
                Node* cur = root;
                //在當前樹的左子樹去找n1和n2節點,如果沒找到就一定在右子樹
                bool tmp1 = _Find(cur->_left,n1->_data);
                bool tmp2 = _Find(cur->_left,n2->_data);
                //n1和n2都在左子樹
                if (tmp1 && tmp2)
                {
                    return _sameAncester1(cur->_left,n1,n2);
                }
                //n1和n2都在右子樹
                else if(!tmp1 && !tmp2)
                {
                    return _sameAncester1(cur->_right,n1,n2);
                }
                //n1與n2一個在左子樹一個在右子樹
                else
                {
                    return root;
                }
            }
        }
        return NULL;
    }
    bool _GetPath2(Node* root,Node* cur,stack<Node*>& s)//在根爲root的樹中查找節點cur的路徑
    {
        if (NULL == root || NULL == cur)//樹爲空
            return false;
        s.push(root);    //將當前節點入棧

        if (cur->_data == root->_data)  //找到路徑
        {
            return true;
        }
        if (_GetPath2(root->_left,cur,s))//在左子樹中找路徑
        {
            return true;
        }
        if (_GetPath2(root->_right,cur,s))//在右子樹中找路徑
        {
            return true;
        }
        s.pop();
        return false;
    }
    Node* _sameAncester2(Node*& root,Node* n1,Node* n2)//求兩個節點的最近公共祖先(時間複雜度:O(N))
    {
        //樹爲空
        if (NULL == root)
        {
            return NULL;
        }
        stack<Node*> s1;
        stack<Node*> s2;
        //用棧s1和s2分別保存節點n1和節點n2的路徑
        _GetPath2(root,n1,s1);
        _GetPath2(root,n2,s2);
        //將多餘的路徑刪除
        while (s1.size() > s2.size())
        {
            s1.pop();
        }
        while(s1.size() < s2.size())
        {
            s2.pop();
        }
        //找s1與s2中不同的節點
        while(s1.top() != s2.top())
        {
            s1.pop();
            s2.pop();
        }
        return s1.top();
    }
    bool _GetPath3(Node* root,Node* cur,vector<Node*>& v)
    {
        if (NULL == root || NULL == cur)//樹爲空
            return false;
        v.push_back(root);

        if (cur->_data == root->_data)  //找到路徑
        {
            return true;
        }
        else
        {
            bool ret = false;
            if (root->_left && !ret)//在左子樹中找路徑
            {
                ret = _GetPath3(root->_left,cur,v);
            }
            if (root->_right && !ret)//在右子樹中找路徑
            {
                ret = _GetPath3(root->_right,cur,v);
            }
            if (ret == false && v.size() != 0)//如果不是正確路徑上的節點,就去除該節點
            {
                v.pop_back();
            }
            return ret;
        }
    }
    Node* _sameAncester3(Node*& root,Node* n1,Node* n2)
    {
        //樹爲空
        if (NULL == root)
        {
            return NULL;
        }

        //定義容器
        vector<Node*> v1;
        vector<Node*> v2;

        //用容器v1和v2分別保存節點n1和節點n2的路徑
        _GetPath3(root,n1,v1);
        _GetPath3(root,n2,v2);

        ////從下往上找
        ////定義指向查找節點的迭代器指針
        //vector<Node*>::iterator it1 = --v1.end();
        //vector<Node*>::iterator it2 = --v2.end();
        ////將多餘的路徑節點刪除掉
        //while(v1.size() > v2.size())
        //{
        //  --it1;          //相應的迭代器要向前移
        //  v1.pop_back();
        //}
        //while(v2.size() > v1.size())
        //{
        //  --it2;          //相應的迭代器要向前移
        //  v2.pop_back();
        //}

        ////找v1與v2中不同的節點
        //while(it1 >= v1.begin() && it2 >= v2.begin())
        //{
        //  if (*it1 == *it2)
        //  {
        //      return *it1;
        //  }
        //  --it1;
        //  --it2;
        //}

        //從上往下找
        //定義指向查找節點的迭代器指針
        vector<Node*>::iterator it1 = v1.begin();
        vector<Node*>::iterator it2 = v2.begin();
        //找v1與v2中不同的節點
        while(it1 != v1.end() && it2 != v2.end())
        {
            if (*it1 != *it2)//找到第一個不相同的節點,返回前一個節點
            {
                return *(--it1);
            }
            ++it1;
            ++it2;
        }
        //處理特殊情況,例如:v1:1->2->3    v2:1->2
        if (it1 == v1.end() || it2 == v2.end())
            return *(--it1);

        return NULL;
    }

9、判斷一棵二叉樹是否是平衡二叉樹
算法實現

bool IsCompleteBT()//判斷一棵樹是否是完全二叉樹
    {
        bool flag = true;//判斷一棵子樹是否爲一棵完全二叉樹的標記
        queue<Node*> q;  //運用隊列進行層序遍歷
        q.push(_root);

        while(!q.empty())
        {
            Node* front = q.front();//保存隊首節點
            q.pop();               //將隊首元素出隊

            /*如果隊首元素的左樹爲空,將標誌置爲false,如果層序遍歷\
             \後面的節點還有子樹,說明不是完全二叉樹*/
            if (NULL == front->_left)
            {
                flag = false;
            }
            else
            {
                /*如果flag爲假,說明之前已經有節點的孩子爲空,又因當前\
                \節點的左孩子不爲空,說明不是完全二叉樹*/
                if (flag == false)
                {
                    return false;
                }
                q.push(front->_left);//繼續向後探索
            }

            /*如果隊首元素的右樹爲空,將標誌置爲false,如果層序遍歷\
             \後面的節點還有子樹,說明不是完全二叉樹*/
            if (NULL == front->_right)
            {
                flag = false;
            }
            else
            {
                /*如果flag爲假,說明之前已經有節點的孩子爲空,又因當前\
                \節點的右孩子不爲空,說明不是完全二叉樹*/
                if (flag == false)
                {
                    return false;
                }
                q.push(front->_right);//繼續向後探索
            }
        }
        return true;//能走到這裏說明一定是完全二叉樹
    }

10、求二叉樹中最遠的兩個節點的距離
外部接口:

int RemoteDistance()   //求二叉樹中最遠的兩個節點的距離
    {
        int tmp = 0;
        return _RemoteDistance1(_root,tmp);
    }

算法實現:

int _RemoteDistance1(Node* root,int& distance)
    {
        if (NULL == root)
        {
            return 0;
        }
        int tmp = _Depth(root->_left)+_Depth(root->_right);//遞歸去求每棵子樹的最遠距離

        if (distance < tmp)//用全局變量保存每棵子樹的最遠距離,並不斷更新
        {
            distance = tmp;
        }
        return distance;
    }

11、由前序遍歷和中序遍歷重建二叉樹
外部接口:

Node* Rebuild(T* prevOrder,T* inOrder,int n) //根據前序序列和中序序列重建二叉樹
    {
        assert(prevOrder && inOrder);

        int prevIndex = 0;   //指向前序序列指針的下標
        int inBegin = 0;     //中序序列的首位置的下標
        int inEnd = n-1;     //中序序列的末位置的下標(閉區間)

        _root = _Rebuild(prevOrder,prevIndex,inOrder,inBegin,inEnd,n);
        return _root;
    }

算法實現:

Node* _Rebuild(T* prevOrder,int& prevIndex,T* inOrder,int inBegin,int inEnd,int n)
    {
        Node* root = NULL;
        if (prevIndex < n)//當前序序列的下標<n時才創建新結點
        {
            root = new Node(prevOrder[prevIndex]);
        }
        if (NULL == root)//遞歸的返回條件
        {
            return NULL;
        }

        if (inBegin == inEnd)//要重建樹中只有一個節點
        {
            return root;
        }
        else
        {
            //在中序序列中找到前序序列中的根
            int i = inBegin;
            for ( ; i <= inEnd; ++i)
            {
                if (prevOrder[prevIndex] == inOrder[i])
                    break;
            }

            if (i > inEnd)//說明中序序列中沒找到前序序列中的根,所給序列不匹配
            {
                throw invalid_argument("所給序列不匹配!");//直接拋異常
            }

            //將根節點作爲分割線,將區間分爲左右兩部分,分別遞歸重建左右子樹
            root->_left = _Rebuild(prevOrder,++prevIndex,inOrder,inBegin,i-1,n);
            root->_right = _Rebuild(prevOrder,++prevIndex,inOrder,i+1,inEnd,n);

            return root;
        }
    }

12、判斷一棵樹是否是完全二叉樹
算法實現:

bool IsCompleteBT()//判斷一棵樹是否是完全二叉樹
    {
        bool flag = true;//判斷一棵子樹是否爲一棵完全二叉樹的標記
        queue<Node*> q;  //運用隊列進行層序遍歷
        q.push(_root);

        while(!q.empty())
        {
            Node* front = q.front();//保存隊首節點
            q.pop();               //將隊首元素出隊

            /*如果隊首元素的左樹爲空,將標誌置爲false,如果層序遍歷\
             \後面的節點還有子樹,說明不是完全二叉樹*/
            if (NULL == front->_left)
            {
                flag = false;
            }
            else
            {
                /*如果flag爲假,說明之前已經有節點的孩子爲空,又因當前\
                \節點的左孩子不爲空,說明不是完全二叉樹*/
                if (flag == false)
                {
                    return false;
                }
                q.push(front->_left);//繼續向後探索
            }

            /*如果隊首元素的右樹爲空,將標誌置爲false,如果層序遍歷\
             \後面的節點還有子樹,說明不是完全二叉樹*/
            if (NULL == front->_right)
            {
                flag = false;
            }
            else
            {
                /*如果flag爲假,說明之前已經有節點的孩子爲空,又因當前\
                \節點的右孩子不爲空,說明不是完全二叉樹*/
                if (flag == false)
                {
                    return false;
                }
                q.push(front->_right);//繼續向後探索
            }
        }
        return true;//能走到這裏說明一定是完全二叉樹
    }

13、求二叉樹的鏡像
外部接口:

void GetMirrorTree()//求二叉樹的鏡像
    {
        _GetMirrorTree(_root);
    }

算法實現:

void _GetMirrorTree(Node* root)//求二叉樹的鏡像
    {
        if (NULL == root)
        {
            return;
        }
        swap(root->_left,root->_right);//交換左右子樹
        _GetMirrorTree(root->_left); //遞歸遍歷左子樹
        _GetMirrorTree(root->_right);//遞歸遍歷右子樹
    }

14、將二叉搜索樹轉換爲一個已排序的雙向鏈表。要求不能創建任何新的節點,只能調整樹中節點指針的指向
外部接口:

void MakeSearchToList()/*將二叉搜索樹轉換成一個排序的雙向鏈表。\
                            要求不能創建任何新的結點,只能調整樹中結點指針的指向。*/
    {
        Node* cur = _root;
        //尋找最左節點
        while (cur->_left)
        {
            cur = cur->_left;
        }

        Node* prev = NULL;
        _MakeSearchToList1(_root,prev);
        //_MakeSearchToList2(_root);

        while(cur)//打印鏈表(樹的結構變了之後析構會死循環)
        {
            cout<<cur->_data<<"<->";
            cur = cur->_right;
        }
        cout<<"null";
    }

算法實現:

void _MakeSearchToList1(Node* root,Node*& prev)
    {
        if (NULL == root)//如果當前樹爲空,則直接返回
            return;

        _MakeSearchToList1(root->_left,prev);//遞歸左子樹

        //進行轉換
        root->_left = prev;//root第一次指向最左節點
        if (prev)//如果上一個節點不爲空,就將上一個節點的右指針指向當前節點root
        {
            prev->_right = root;
        }
        prev = root;//更新保存上一個節點

        _MakeSearchToList1(root->_right,prev);//遞歸右子樹
    }
    void _InOrderList(Node* root,queue<Node*>& q)//將樹的中序遍歷的節點入到隊列中
    {
        if (NULL == root)
            return;
        _InOrderList(root->_left,q);//遞歸左子樹
        q.push(root);              //將當前節點入隊
        _InOrderList(root->_right,q);//遞歸右子樹
    }
    void _MakeSearchToList2(Node* root)
    {
        if (NULL == root)//如果當前樹爲空則直接返回
            return;

        queue<Node*> q;
        _InOrderList(root,q);//將所給樹的中序序列push到隊列q中

        Node* head = q.front();//將隊首節點(即所給樹的最左節點)保存起來
        q.pop();

        head->_left = NULL;//將隊首節點的左指針置空
        Node* prev = head;//用prev保存當前節點

        while(!q.empty())//當隊列不空時進入循環
        {
            Node* front = q.front();//獲取隊首節點
            q.pop();               //將隊首節點出隊

            prev->_right = front;   //將前一個節點的右指針指向隊首節點
            front->_left = prev;    //將隊首節點的左指針指向上一個節點

            front->_right = NULL;   //將隊首節點的右指針置空
            prev = front;          //更新保存上一個節點
        }
    }
發佈了122 篇原創文章 · 獲贊 269 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章