做二叉树已经有两天的时间了,这两天在牛客上把剑指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.二叉树中和为某一值的路径

解题思路:

 

 

未完待续。。。。持续更新