二叉樹題目總結(搞定機試、Offer收割一篇就夠了)

做二叉樹已經有兩天的時間了,這兩天在牛客上把劍指offer中和牛客考/保研機試中的樹的題都做了。emmm,感受頗深。下面開始正題。

樹的本質就是遞歸!遞歸!遞歸!

 

目錄

一、樹的結構,給出leetcode常用的標準板子;

二、遍歷方法

1.前序遍歷

2.中序遍歷

3.後序遍歷

4.層次遍歷

三、相關題目(以劍指offer爲準)

1.二叉樹的鏡像

2.樹的子結構

3.從上往下打印二叉樹

3.1 相似題目1

3.2 相似題目2 (按之字形順序打印二叉樹)

4.二叉搜索樹的後序遍歷序列

5.二叉樹中和爲某一值的路徑


一、樹的結構,給出leetcode常用的標準板子;


struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};

二、遍歷方法

二叉樹最重要的就是四種遍歷方法,只要掌握了這四種遍歷方法,剩下就是套題的過程了。

1.前序遍歷

void preOrder(TreeNode *Head)
{
	if(Head==NULL)
		return ;
	visit(Head);
	preOrder(Head->leftChild);
	preOrder(Head->rightChild);
	return ;
};

2.中序遍歷

void inOrder(TreeNode *Head)
{
	if(Head==NULL)
		return ;
	inOrder(Head->leftChild);
	visit(Head);
	inOrder(Head->rightChild);
	return ;
};

3.後序遍歷

void postOrder(TreeNode *Head)
{
	if(Head==NULL)
		return ;
	postOrder(Head->leftChild);
	postOrder(Head->rightChild);
	visit(Head);
	return ;
};

4.層次遍歷

 vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> vec;
        queue<TreeNode*>q;
        if(root==NULL)
            return  vec;
        q.push(root);
        while(!q.empty())
        {
            TreeNode *r=q.front();
            vec.push_back(r->val);
            q.pop();
            if(r->left!=NULL)
                q.push(r->left);
            if(r->right!=NULL)
                q.push(r->right);
        }
        return vec;
    }

三、相關題目(以劍指offer爲準)

從熱度高到低排列

1.二叉樹的鏡像

解題思路:從圖中的描述可知,此題就是將二叉樹的左右兩棵子樹進行交換。只要使用前序遍歷就可以完成。

交換過程共分四種情況:父節點不存在;父節點兩棵子樹都存在;父節點兩棵子樹只存在一棵;父節點無子樹。

代碼如下:

void Mirror(TreeNode *pRoot) {
        if(pRoot==NULL)
            return;
        if(pRoot->left!=NULL && pRoot->right!=NULL)
        {
            TreeNode *temp=pRoot->left;
            pRoot->left=pRoot->right;
            pRoot->right=temp;
        }
        else if(pRoot->left!=NULL && pRoot->right==NULL)
        {
            pRoot->right=pRoot->left;
            pRoot->left=NULL;
        }
         else if(pRoot->left==NULL && pRoot->right!=NULL)
        {
            pRoot->left=pRoot->right;
            pRoot->right=NULL;
        }
        else
            return ;
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }

2.樹的子結構

注:這裏的子結構可能大家會有一個歧義,認爲其實就是判斷B是不是A的子樹。其實不然。

如下圖,即使樹2不是樹1的子樹,但是樹1中仍然包含樹2,所以樹2也是樹1的子結構。

解題思路:通過對子結構的認識,相信大家也很快知道了這個題就是判斷B包不包含在A中。如果拋棄樹的概念,給定大家一個字符串A和一個字符串B,問A中包不包含B,相信很快有同學想到要用str.find(string s)這個函數。如果我們認真分析一下。我們要將A和B進行匹配。一種簡單的辦法(KMP除外)就是先找到B[0]在A中的位置,如果B[0]匹配了,就去匹配B[1],B[2]...,如果不成功,返回到第一個B[0]匹配的位置的下一個位置,繼續尋找匹配,如果匹配成功,就返回true;

這是在字符串進行匹配。那麼換成樹呢?

依舊是這樣的,我們先找到第一個匹配的節點。然後匹配成功,就繼續遞歸子樹,匹配子樹。如果失敗就繼續尋找匹配。

bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        bool result=false;
        if(pRoot1!=NULL && pRoot2!=NULL) //只要有一個樹爲空就返回false
        {
            if(pRoot1->val==pRoot2->val)
            {
                result=doesTree1hasTree2(pRoot1,pRoot2);
            }
            if(!result)
                result=HasSubtree(pRoot1->left,pRoot2);
            if(!result)
                result=HasSubtree(pRoot1->right,pRoot2);
        }
        return result;
    }
    bool doesTree1hasTree2(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot2==NULL) //如果pRoot2遍歷完了,返回true
            return true;
        if(pRoot1==NULL) //如果pRoot2還沒遍歷完但是pRoot1遍歷完了,返回false
            return false;
        if(pRoot1->val!=pRoot2->val)
            return false;
        //如果相等的話,繼續遍歷對應的左子樹和右子樹
        return doesTree1hasTree2(pRoot1->left,pRoot2->left)&& doesTree1hasTree2(pRoot1->right,pRoot2->right);
            
    }

3.從上往下打印二叉樹

解題思路:層次遍歷;代碼在前邊給出了。

3.1 相似題目1

解題思路:這個題相較於上一題多了一個要求,就是要在每一層輸出一行換行。

所以我們要用隊列的特性記錄每一層的節點。

代碼如下:

vector<vector<int> > Print(TreeNode* pRoot) {
            vector<vector<int>> v;
            vector<int> vec;
            queue<TreeNode *> q;
            if(pRoot==NULL)
                return v;
            q.push(pRoot);
            int cur,len;
            while(!q.empty())
            {
                cur=0;        //記錄分層
                len=q.size(); //記錄分層
                while(cur++<len)
                {
                    TreeNode* root=q.front();
                    q.pop();
                    vec.push_back(root->val);
                    if(root->left!=NULL)
                        q.push(root->left);
                    if(root->right!=NULL)
                        q.push(root->right);
                }
                v.push_back(vec);
                vec.clear();
            }
            return v;
        }

3.2 相似題目2 (按之字形順序打印二叉樹

解題思路:這個題相對於3.1的變種來說,又多了一個要求,那就是按照之字形打印。當然細心的同學也能很快發現一個規律:層數是奇數的從左向右打印。層數是偶數的從右往左打印。所以換湯不換藥。只要輸出的時候判斷一下就好了。代碼如下:

vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int>> v,tempv; //最後的返回值
        vector<int> vec;       //臨時存放每一層節點的值
        queue<TreeNode *> q;   //隊列
        int pos,len;
        if(pRoot==NULL)
            return v;
        q.push(pRoot);
        while(!q.empty())
        {
            pos=0;
            len=q.size(); //每一層的節點數量
            while(pos++<len)
            {
                TreeNode *root=q.front();
                q.pop();
                vec.push_back(root->val);
                if(root->left!=NULL)
                    q.push(root->left);
                if(root->right!=NULL)
                    q.push(root->right);
            }
            v.push_back(vec);
            vec.clear();
        }
        for(int i=0;i<v.size();i++)
        {
            if((i+1)%2==1)//(i+1)爲奇數,從左到右輸出;爲偶數,從右到左輸出
                tempv.push_back(v[i]);
            else //如果是偶數就逆序輸出
            {
                vec.clear();
                for(int j=v[i].size()-1;j>=0;j--)
                {
                    vec.push_back(v[i][j]);
                }
                tempv.push_back(vec);
            } 
        }
        return tempv;
    }

4.二叉搜索樹的後序遍歷序列

解題思路:這個題大家可能楞的一看,完全沒有思路。。。。但是我們可以把抽象問題具體化。畫圖寫後序遍歷來試一試。我寫遞歸的原則就是我要先懂,然後計算機才能懂。

注意:這裏邊不是二叉樹,而是 二叉搜索樹。二叉搜索樹是一種特殊的二叉樹。樹的左孩子都<=他,樹的右孩子都>=他。

通過上圖可以看到,後序遍歷的最後一個節點就是樹的根節點。而我們又知道,二叉搜索樹的左子樹都<=他,右子樹都>=他,所以我們可以發現一個規律, 在後序遍歷中5的後邊是7,也就是根節點的右子樹,都>5,接下來就是3,也就到了左子樹部分。而2.3.4都是小於5的。那麼這樣我們這個題就有解了:

我們可以先把後序reverse反序一下。然後第一個節點就是根節點,緊接着我們找根節點後的第一個小於根節點的位置index。爲什麼?

因爲這個節點把樹分成了兩部分,一部分比樹根大,一部分比樹根小。so,,如果我們在index後發現了比樹根大的,那麼很顯然這不成立。然後我們繼續遞歸下去。就會發現答案。

    bool VerifySquenceOfBST(vector<int> sequence) {
        //後續遍歷的最後一個節點是根節點,可以把搜索樹分成兩部分
        //找到第一個比根節點小的集和,因此劃成左右子樹,左集合裏不能有比根節點大的。如果有return false
        
        //算法流程:
        //判斷vector是否爲空,不爲空,繼續判斷,若爲空 return false
        //拿出右邊第一個元素(根節點)c
        //從右往左找到第一個比根節點小的節點下標index
        //劃分集和,把(0,index)劃分爲一組,判斷這組裏是否有比根節點c大的,如果有return false
        //如果沒有,繼續劃分,直到找不到比c小的,return true;
        if(sequence.size()==0)
            return false;
        int c=sequence[sequence.size()-1]; //根節點
        int index; //找到第一個比c根節點小的節點
        for(index=sequence.size()-2;index>=0;index--)
        {
            if(sequence[index]<c)
                break;
        }
        if(index<0) //沒有找到比c小的節點,return true;
            return true;
        vector<int> temp;
        for(int i=0;i<=index;i++)
        {
            if(sequence[i]>c)
                return false;
            temp.push_back(sequence[i]);
        }
        return VerifySquenceOfBST(temp);
    }

5.二叉樹中和爲某一值的路徑

解題思路:

 

 

未完待續。。。。持續更新

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