做二叉樹已經有兩天的時間了,這兩天在牛客上把劍指offer中和牛客考/保研機試中的樹的題都做了。emmm,感受頗深。下面開始正題。
樹的本質就是遞歸!遞歸!遞歸!
目錄
一、樹的結構,給出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.二叉樹中和爲某一值的路徑
解題思路:
未完待續。。。。持續更新