【劍指offer(三)】:二叉樹

1、二叉搜索樹第k大個結點(面試54)

  • 題目描述

   給定一顆二叉樹,找出第K大的節點,這個第K大的節點是從小到大的第K個節點。例如:如下圖中,按節點大小排序,第三大節點的值爲9。

  • 思路

二叉搜索樹的中序遍歷就是排序的,所以用中序遍歷,每一次中間的時候判斷是否等於k即可。

  • 代碼實現
struct BinaryTreeNode
{
    int m_nValue;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
};
BinaryTreeNode *KthNodeCore(BinaryTreeNode* root, unsigned int k){
    BinaryTreeNode *target= nullptr;

    if(root->m_pLeft!= nullptr){
        target=KthNodeCore(root->m_pLeft,k);
    }

    if(target== nullptr){
        if(k==1){
            target==root;
        }

        --k;
    }

    if(target->m_pRight!= nullptr && target== nullptr){
        target=KthNodeCore(root->m_pRight,k);
    }

    return target;
}

BinaryTreeNode *KthNode(BinaryTreeNode* root, unsigned int k){
    if(root== nullptr || k==0){
        return nullptr;
    }

    return KthNodeCore(root,k);
}

2.0、 從上往下打印二叉樹(面試32)

  • 題目描述

從上往下打印出二叉樹的每個節點,同層節點從左至右打印。

  • 思路

實際考察樹的層次遍歷(廣度優先遍歷)算法,從上往下遍歷二叉樹,本質上就是廣度優先遍歷二叉樹。
            10
          /      \
         6         14
        /\          /\
      4  8     12  16
用按層遍歷二叉樹的方法,

(1)按層遍歷的順序決定首先應該打印根節點,所以從根節點10開始分析。
(2)打印完根節點10,要打印它的左右子節點6,14。所以在遍歷到左右子節點時,先將它們裝入容器中,此時容器內存了6,14
(3) 先打印左節點6,此時6的左右子節點4,8也進入了容器,容器內有3個元素,14,4,8
(4)從容器內取出14並打印,然後將14的左右子節點12,16存入容器中,此時容器中有4個元素,按順序是4,8,12,16
(5)容器中剩餘的元素是葉子結點,無左右節點,按順序從容器中依次取出並打印即可。
綜上分析,這種先進先出的容器,是隊列,所以我們利用隊列做臨時存儲器來完成打印。

操作步驟圖示:



原文鏈接:https://blog.csdn.net/orangefly0214/article/details/83743664

  • 代碼實現
struct BinaryTreeNode
{
    int m_nValue;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
};


void PrintFromTopToBottom(BinaryTreeNode *pTreeRoot)
{
    if(pTreeRoot== nullptr){
        return ;
    }

    deque<BinaryTreeNode*> dequeTreeNode;

    dequeTreeNode.push_back(pTreeRoot);
    while(!dequeTreeNode.empty()){
        BinaryTreeNode *pNode=dequeTreeNode.front();
        dequeTreeNode.pop_front();
        cout<<pNode->m_nValue<<" ";
        if(pNode->m_pLeft){
            dequeTreeNode.push_back(pNode->m_pLeft);
        }
        if(pNode->m_pRight){
            dequeTreeNode.push_back(pNode->m_pRight);
        }
    }
    cout<<endl;
}

2.1、二叉樹打印成多行(面試32)

  • 題目描述

按照之字形順序打印二叉樹。

            10
          /      \
         6         14
        /\          /\
      4  8     12  16

10

14   6

4  8  12  16

  • 思路

用一個隊列保存將要打印的節點。這裏我們需要兩個變量:一個變量表示當前層中還沒打印的節點個數;下一個變量表示下一層中節點的數目。

  • 代碼實現
void Print(BinaryTreeNode *pRoot)
{
    if(pRoot== nullptr){
        return ;
    }

    deque<BinaryTreeNode*> nodes;
    nodes.push_back(pRoot);

    int nextLevel=0;
    int toBePrinted=1;
    while(!nodes.empty()){
        BinaryTreeNode *pNode=nodes.front();

        nodes.pop_front();
        --toBePrinted;

        cout<<pNode->m_nValue<<" ";

        if(pNode->m_pLeft){
            nodes.push_back(pNode->m_pLeft);
            ++nextLevel;
        }

        if(pNode->m_pRight){
            nodes.push_back(pNode->m_pRight);
            ++nextLevel;
        }


        if(toBePrinted==0){
            cout<<endl;
            toBePrinted=nextLevel;
            nextLevel=0;
        }
    }
}

2.2、按之字形順序打印二叉樹(面試32)

  • 題目描述

按照之字形順序打印二叉樹。

            10
          /      \
         6         14
        /\          /\
      4  8     12  16

10

6  146

4  8  12  16

  • 思路

按之字形打印二叉樹需要兩個棧,我們在打印某個棧時,把下一個節點保存到相應的棧中。如果保存的爲奇數層,先保存左節點,在保存右子節點;如果保存的是偶數層,先保存右子節點,在保存左子節點。

  • 代碼實現
void Print1(BinaryTreeNode *pRoot)
{
    if(pRoot== nullptr){
        return ;
    }

    stack<BinaryTreeNode*> levels[2];
    int current=0;
    int next=1;
    levels[current].push(pRoot);
    while(!levels[0].empty() ||!levels[1].empty()){
        BinaryTreeNode *pNode=levels[current].top();
        levels[current].pop();
        cout<<pNode->m_nValue<<" ";

        if(current==0){
            if(pNode->m_pLeft!= nullptr){
                levels[next].push(pNode->m_pLeft);
            }
            if(pNode->m_pRight!= nullptr){
                levels[next].push(pNode->m_pRight);
            }
        }else{
            if(pNode->m_pRight!= nullptr){
                levels[next].push(pNode->m_pRight);
            }

            if(pNode->m_pLeft!= nullptr){
                levels[next].push(pNode->m_pLeft);
            }
        }

        if(levels[current].empty()){
            cout<<endl;
            current=1-current;
            next=1-next;
        }
    }
}

3、數據流中位數(面試41)

  • 題目描述

獲取數據流中的中位數

  • 思路

用一個最大堆實現左邊的數據容器,最小堆實現右邊的容器。往堆中插入數據的時間爲O(logn),獲得中位數的時間爲O(1)。

具體實現如下:

首先,要保證數據平均分配到兩個堆中,所以兩個堆的數目只差不超過1。爲了實現平橫,可以在數目是偶數時,把新數據插入到最小堆,否則插入最大堆;

其次,把數據插入最大堆,接着把最大堆的最大數插入最小堆。由於最終插入最小堆的數字是最大堆的最最大數子,這樣就保證最大堆的數字都大於最下堆的數字。其他情況類似。

  • 代碼實現
template <typename T>
class DynamicArray{
public:
    void Insert(T num){
        if(((min.size()+max.size())&1)==0){
            if(max.size()>0 && num<max[0]){
                max.push_back(num);
                push_heap(max.begin(),max.end(),less<T>());

                num=max[0];

                pop_heap(max.begin(),max.end());
                max.pop_back();
            }

            min.push_back(num);
            push_heap(min.begin(),max.end(),greater<T>());
        }else{
            if(min.size()>0 && min[0]<num){
                min.push_back(num);
                push_heap(min.begin(),min.end(),greater<T>());

                num=min[0];

                pop_heap(min.begin(),min.end(),greater<T>());
                min.pop_back();
            }

            max.push_back(num);
            push_heap(max.begin(),max.end(),less<T>());
        }
    }

    T GetMedian(){
        int size=min.size()+max.size();
        if(size==0){
            cout<<("No numbers are available")<<endl;
            exit(0);
        }

        T median=0;
        if(size&1==1){
            median=min[0];
        }else{
            median=(min[0]+max[0])/2;
        }

        return median;
    }
private:
    vector<T> min;
    vector<T> max;
};

4、二叉樹中和爲某一值的路徑(面試34)

  • 題目描述

輸入一個二叉樹,打印二叉樹中節點值的和爲輸入整數的所有路徑。

  • 思路

對於樹的遍歷一般就是深度遍歷和廣度遍歷下四種中的一種,從根節點往下找到葉節點形成一條路徑,若是和爲給定的值這個路徑便是需要找的路徑,從根節點到葉節點邊訪問邊相加判斷可以採用先序遍歷。
(1)若根節點的值大於給定的值或者根節點爲空,則清空路徑;
(2)若根節點的值等於給定的值,需要判斷當前節點是否爲葉子節點,若爲葉子節點,則是需要找的一條路徑,若不是,將保存的節點全部清空。
(3)若根節點的和小於給定的值,則分別去訪問其左右子樹。

  • 代碼實現
void FindPath(BinaryTreeNode *pRoot,int exceptedSum,
              vector<int> &path,int currentSum){
    currentSum+=pRoot->m_nValue;
    path.push_back(pRoot->m_nValue);

    bool isLeaf=pRoot->m_pRight== nullptr && pRoot->m_pLeft== nullptr;
    if(currentSum==exceptedSum && isLeaf){
        for(auto it=path.begin();it!=path.end();++it){
            cout<<*it<<" ";
        }
        cout<<endl;
    }

    if(pRoot->m_pLeft!= nullptr){
        FindPath(pRoot->m_pLeft,exceptedSum,path,currentSum);
    }

    if(pRoot->m_pRight!= nullptr){
        FindPath(pRoot->m_pRight,exceptedSum,path,currentSum);
    }

    path.pop_back();
}

void FindPath(BinaryTreeNode *pRoot,int exceptedSum){
    if(pRoot== nullptr){
        return ;
    }

    vector<int> path;
    int currentSum=0;
    FindPath(pRoot,exceptedSum,path,currentSum);
}

5、重建二叉樹(面試7)

  • 題目描述

輸入一個搜索二叉樹的前序和中序遍歷,請重建該二叉樹。

  • 思路

若前序或者中序序列爲空或者長度不等則返回空;創建根節點,前序序列的一個結點爲根節點,在中序序列中找到根節點位置,可以分別得到左、右子樹的前序和中序序列,然後遞歸重建左、右子樹;使用輔助空間,保存被分割的前序和中序序列。

  • 代碼實現
BinaryTreeNode* ConstructCore(int *startPreOrder,int *endPreOrder,
                              int *startInOrder,int *endInOrder)
{
    int rootValue=startPreOrder[0];
    BinaryTreeNode* root=new BinaryTreeNode();
    root->m_nValue=rootValue;
    root->m_pLeft=root->m_pRight= nullptr;

    if(startPreOrder==endPreOrder){
        if(startInOrder==endInOrder && *startPreOrder==*startInOrder){
            return root;
        }else{
            throw std::exception();
        }
    }

    int *rootInOrder=startInOrder;
    while(rootInOrder<=endInOrder && *rootInOrder!=rootValue){
        ++rootInOrder;
    }

    if(rootInOrder==endInOrder && *rootInOrder!=rootValue){
        throw std::exception();
    }

    int leftLength=rootInOrder-startInOrder;
    int *leftPreoderEnd=startPreOrder+leftLength;
    if(leftLength>0){
        root->m_pLeft=ConstructCore(startPreOrder+1,leftPreoderEnd,
                                    startInOrder,rootInOrder-1);
    }

    if(leftLength<endPreOrder-startPreOrder){
        root->m_pRight=ConstructCore(leftPreoderEnd+1,endPreOrder,
                                     rootInOrder+1,endInOrder);
    }

    return root;
}

BinaryTreeNode* Construct(int *preOrder,int *inOrder,int length)
{
    if(preOrder== nullptr || inOrder== nullptr || length<=0){
        return nullptr;
    }

    return ConstructCore(preOrder,preOrder+length-1,
                         inOrder,inOrder+length-1);
}

6、樹的子結構(面試26)

  • 題目描述

輸入兩顆二叉樹A和B,判斷B是否爲A的子結構。

  • 思路

要查找樹A中是否存在和樹B結構一樣的子樹,思路是第一步:先在樹A中查找與根結點的值一樣的結點,這實際就是樹的先序遍歷,當樹A和樹B爲空時,定義相應的輸出。如果樹A某一結點的值和樹B頭結點的值相同,則調用findnext,做第二步判斷。第二步是判斷樹A中以R爲根結點的子樹是不是和樹B具有相同的結構,使用遞歸的方法考慮:如果結點R和樹B的根結點不相同則返回false,如果相同,則遞歸判斷它們的左右結點的值是不是相同。遞歸終止條件是我們到達樹A或者樹B的葉結點。

  • 代碼實現
bool Equal(double num1,double num2)
{
    if (num1-num2>-0.0000001 && num1-num2<0.0000001){
        return true;
    }else{
        return false;
    }
}

bool DoesTreeHaveTree(BinaryTreeNode *pRoot1,BinaryTreeNode *pRoot2)
{
    if(pRoot2== nullptr){
        return true;
    }

    if(pRoot1== nullptr){
        return false;
    }

    if (!Equal(pRoot1->m_dbValue,pRoot1->m_dbValue)){
        return false;
    }

    return DoesTreeHaveTree(pRoot1->m_pLeft,pRoot2->m_pLeft)&&
           DoesTreeHaveTree(pRoot1->m_pRight,pRoot2->m_pRight);
}

bool HasSubtree(BinaryTreeNode *pRoot1,BinaryTreeNode *pRoot2)
{
    bool result=false;

    if(pRoot1!= nullptr && pRoot2!= nullptr){
        if(Equal(pRoot1->m_dbValue,pRoot2->m_dbValue)){

        }

        if(!result){
            result=HasSubtree(pRoot1->m_pLeft,pRoot2);
        }

        if(!result){
            result=HasSubtree(pRoot1->m_pRight,pRoot2);
        }
    }

    return result;
}

7、二叉樹的鏡像(面試27)

  • 題目描述

輸入一個二叉樹,輸出其鏡像。

  • 思路

先前序遍歷這顆樹的每個節點,如果遍歷到的節點有子節點,就交換它的兩個子節點。當交換玩所有非葉節點的左、右子節點,就得到了樹的鏡像。

  • 代碼實現
void MirrorRecursively(BinaryTreeNode *pNode)
{
    if(pNode== nullptr){
        return ;
    }

    BinaryTreeNode *pTmp= pNode->m_pLeft;
    pNode->m_pLeft=pNode->m_pRight;
    pNode->m_pRight=pTmp;

    if(pNode->m_pLeft){
        MirrorRecursively(pNode->m_pLeft);
    }

    if(pNode->m_pRight){
        MirrorRecursively(pNode->m_pRight);
    }
}

8、二叉搜素樹的後序遍歷序列(面試33)

  • 題目描述

輸入一個二叉樹,判斷是否爲某二叉搜索樹後序遍歷的結果。

  • 思路

在後序遍歷得到的序列中,最後一個數字是樹的根結點的值。數組中前面的數字可以分兩部分:第一部分是左子樹節點的值,它們都比根結點的值小;第二部分是右子樹節點的值,它們都比根結點的值大;根據根結點的值劃分開左子樹節點和右子樹節點,用同樣的方法分別處理左子樹和右子樹。

  • 代碼實現
bool VerifySquenceOfBST(int sequence[],int length)
{
    if(sequence== nullptr||length<=0){
        return false;
    }

    int root=sequence[length-1];
    //在二叉樹中左子樹節點的值均小於根節點
    int i=0;
    for(;i<length-1;++i){
        if(sequence[i]>root){
            break;
        }
    }
    //二叉樹中右子樹均大於根節點
    int j=i;
    for(;j<length-1;++j){
        if(sequence[j]<root){
            return false;
        }
    }

    //判斷左子樹是否爲二叉搜索樹
    bool left=true;
    if(i>0){
        left=VerifySquenceOfBST(sequence,i);
    }

    //判斷右子樹是否爲二叉搜索樹
    bool right=true;
    if(j<length-1){
        right=VerifySquenceOfBST(sequence+i,length-i-1);
    }

    return left&&right;
}

9、二叉搜索樹與雙向鏈表(面試36)

  • 題目描述

輸入一個二叉搜索樹,將其轉化爲排序的雙向鏈表。要求不能創建節點,只能在書中的節點中進行調整。

  • 思路

1.根據二叉搜索樹和題目要求的特點(二叉搜索樹左<根<右,題目要求要轉換爲排序的雙向鏈表)使用中序遍歷算法遍歷樹的每個節點(中序遍歷的特點是按照從小到大的順序遍歷二叉樹每個節點)
2.聲明一個永遠指向鏈表尾結點的指針pLastNodeInList 和 一個在每次新的函數中都指向當前待插入鏈表節點的當前節點指針pCur,主函數調用排序函數
3.遞歸調用排序函數直至找到最左子節點
4.將當前節點插入鏈表,並更新尾結點指針pLastNodeInList
5.遞歸處理右子樹

  • 代碼實現
void ConvertNode(BinaryTreeNode *pNode,BinaryTreeNode **pLastNodeInList){
    if(pNode== nullptr){
        return ;
    }

    BinaryTreeNode *pCurrent=pNode;

    if(pCurrent->m_pLeft!= nullptr){
        ConvertNode(pCurrent->m_pLeft,pLastNodeInList);
    }

    pCurrent->m_pLeft=*pLastNodeInList;
    if(*pLastNodeInList!= nullptr){
        (*pLastNodeInList)->m_pRight=pCurrent;

        *pLastNodeInList=pCurrent;

        if(pCurrent->m_pRight!= nullptr){
            ConvertNode(pCurrent->m_pRight,pLastNodeInList);
        }
    }
}

BinaryTreeNode *Convert(BinaryTreeNode *pRootOfTree){
    BinaryTreeNode *pLastNodeInList= nullptr;
    ConvertNode(pRootOfTree,&pLastNodeInList);

    //pLastNodeInList指向雙向鏈表的爲節點
    BinaryTreeNode *pHeadOflist=pLastNodeInList;
    while(pHeadOflist!= nullptr &&pHeadOflist->m_pLeft!= nullptr){
        pHeadOflist=pHeadOflist->m_pLeft;
    }

    return pHeadOflist;
}

10、二叉樹的深度(面試55)

  • 題目描述

輸入一個二叉樹,求該樹的深度。

  • 思路

(1)如果一棵樹只有一個結點,它的深度爲1。

(2)如果根結點只有左子樹而沒有右子樹,那麼樹的深度應該是其左子樹的深度加1;同樣如果根結點只有右子樹而沒有左子樹,那麼樹的深度應該是其右子樹的深度加1。

(3)如果既有右子樹又有左子樹,那該樹的深度就是其左、右子樹深度的較大值再加1。

  • 代碼實現
int TreeDepth(BinaryTreeNode* pRoot){
    if(pRoot== nullptr){
        return 0;
    }

    int left=TreeDepth(pRoot->m_pLeft);
    int right=TreeDepth(pRoot->m_pRight);

    return max(left,right)+1;
}

11、平衡二叉樹(面試55)

  • 題目描述

輸入一個二叉樹,判斷該樹是否爲平衡二叉樹。

  • 思路

後序遍歷二叉樹,先分別統計左右子樹的深度,再判斷樹是否平衡。

  • 代碼實現
bool IsBalanced1(BinaryTreeNode* root,int* pDepth){
    if(root== nullptr){
        *pDepth=0;
        return true;
    }

    int left,right;

    if(IsBalanced1(root->m_pLeft,&left)&&IsBalanced1(root->m_pRight,&right)){
        int diff=left-right;
        if(diff<=1 && diff>=-1){
            *pDepth=1+max(left,right);
            return true;
        }
    }

    return false;
}

bool IsBalanced(BinaryTreeNode* root){
    int depth=0;
    return IsBalanced1(root,&depth);
}

12、二叉樹的下一個節點(面試8)

  • 題目描述

輸入一個二叉樹和其中一個節點,如何找出中序遍歷的下一個節點。

  • 思路

根據中序遍歷的特點:左根右。可分爲三種情況:1. 一個節點有右子樹,那麼它的下一個節點就是它的右子樹的最左子節點。2.  一個節點沒有右子樹它還是它父節點的左子節點,那麼它的下一個節點就是它的父節點。3.  一個節點沒有右子樹它還是它父節點的右子節點,那麼它的下一個節點就是含有左子節點的祖先節點。

  • 代碼實現
BinaryTreeNode* GetNext(BinaryTreeNode *pNode)
{
    if(pNode== nullptr){
        return nullptr;
    }

    BinaryTreeNode* pNext= nullptr;
    //如果存在右子樹
    if(pNode->m_pRight!= nullptr){
        BinaryTreeNode *pRight=pNode->m_pRight;
        while(pRight->m_pLeft!= nullptr){
            pRight=pRight->m_pLeft;
        }

        pNext=pRight;
    }else if(pNode->m_pParent!= nullptr){//如果不存在右子樹
        BinaryTreeNode* pCurrent=pNode;
        BinaryTreeNode* pParent=pNode->m_pParent;
        while(pParent!= nullptr && pCurrent==pParent->m_pRight){
            pCurrent=pParent;
            pParent=pParent->m_pParent;
        }

        pNext=pParent;
    }

    return pNext;
}

 13、對稱的二叉樹(面試28)

  • 題目描述

判斷一個二叉樹是否爲對稱二叉樹。

  • 思路

自定義後序遍歷:根右左。在考慮葉子節點的left和right的情況下(也就是考慮null), 如果前序遍歷和後序遍歷的結果相同, 那麼二叉樹是對稱的

  • 代碼實現
bool isSymmetrical(BinaryTreeNode *pRoot1,BinaryTreeNode *pRoot2)
{
    if(pRoot1==nullptr && pRoot2== nullptr){
        return true;
    }

    if(pRoot1== nullptr || pRoot2== nullptr){
        return false;
    }

    if(pRoot1->m_nValue!=pRoot2->m_nValue){
        return false;
    }

    return isSymmetrical(pRoot1->m_pLeft,pRoot1->m_pRight) &&
           isSymmetrical(pRoot2->m_pLeft,pRoot2->m_pRight);
}

bool isSymmetrical(BinaryTreeNode *pRoot)
{
    return isSymmetrical(pRoot,pRoot);
}

14、序列化二叉樹(面試37)

  • 題目描述

序列化和反序列化二叉樹。

  • 思路

如果二叉樹的序列化是從根節點開始的,那麼相應的反序列化在根節點的數值讀出來的時候就可以開始了。因此,我們可以根據前序遍歷的順序來序列化二叉樹,因爲前序遍歷是從根節點開始的。在遍歷二叉樹碰到null指針時,這些null指針序列化爲一個特殊的字符。另外,節點的數值之間要用一個特殊字符隔開(在這裏我用的是‘,’)。

  • 代碼實現
void Serialize(BinaryTreeNode *pRoot,ostream &stream){
   if(pRoot== nullptr){
       stream<<"$,";
       return ;
   }

    stream<<pRoot->m_nValue<<',';
    Serialize(pRoot->m_pLeft,stream);
    Serialize(pRoot->m_pRight,stream);
}

void Deserialize(BinaryTreeNode **pRoot,istream &stream){
    int number;
    if(ReadStream(stream,&number)){
        *pRoot=new BinaryTreeNode();
        (*pRoot)->m_nValue=number;
        (*pRoot)->m_pLeft= nullptr;
        (*pRoot)->m_pRight= nullptr;

        Deserialize(&((*pRoot)->m_pLeft),stream);
        Deserialize(&((*pRoot)->m_pRight),stream);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章