11.算法与数据结构——树(递归遍历,层次遍历,按序遍历,二叉查找树)

树就是单链表的子节点发生了变化,就多了一个子节点而已。
LeetCode 默认的树表示方法如下。

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

递归遍历

104. 二叉树的最大深度

递归终点很好确定,就是空节点。
我们每次如果root 是空就返回0,
否则就用1+左右子树深度的最大值

class Solution {
public:
    int maxDepth(TreeNode* root) {
        return root? 1+max(maxDepth(root->left),maxDepth(root->right)):0;

    }
};

110. 平衡二叉树

树平衡的定义是,对于树上的任意节点,其两侧节点的最大深度的差值不得大于 1。
对于每一个节点,我们先看其是否为空,到空节点表示到底了。非空节点用1加上左右深度最大值。
左右子节点是不是已经不平衡,如果已经不平衡就没有必要判断,如果子节点都是平衡的就看他自己是不是平衡的。

class Solution {
public:
    bool isBalanced(TreeNode* root) {
        return judge(root) !=-1;

    }
    int judge(TreeNode* root)//对于每一个节点,判断其左右深度
    {
        if(!root) return 0;//如果为空返回0,表示某一边已经到底
        int left=judge(root->left),right=judge(root->right);  //获取其左右深度
        if(left==-1 ||right==-1 ||abs(left-right)>1)//如果深度差值大于1或者左右子节点已经不平衡,就没有必要继续判断
            return -1;
        
        return 1+max(left,right);//没有到底深度继续加一
    }

543. 二叉树的直径

在这里插入图片描述

这一题主要做一个区分。我们在计算二叉树的深度的时候,是用1+max(左深,右深)。
但是他这个直径,是要用1+左深+右深
所以我们不光要计算其深度,还要维护一个变量来比较max(maxd,l+r)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int diameterOfBinaryTree(TreeNode* root) 
    {
        int maxd=0;
        cal(root,maxd);
        return maxd;
    }
    int cal(TreeNode* root,int &maxd)
    {
        if(!root) return 0;
        int l=cal(root->left,maxd),r=cal(root->right,maxd);
        maxd=max(l+r,maxd);
        return 1+max(l,r);
    }
};

1110.二叉树的删除

在这里插入图片描述
题目的意思是删除了这个节点之后,我们要处理一些指针指向空,并且这个节点的左右节点又独立出来成为一个新树。
所以我们对于每一个节点都需要判断其是否在需要删除的组里面,题目给出的vector& to_delete格式为vector,不方便我们进行查找,所以我们想到将其转化成一个哈希集,这样的就能在O(1)的时间里面判断节点是否在容器内。

注:这里要使用后续遍历,因为要保证优先遍历到所有节点再删除,如果采用前序或者中序遍历,遍历的过程中,万一将其删除了,后续没有办法继续遍历被删除节点的子节点了

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
private:
    vector<TreeNode*> ans;
public:
    vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) 
    {
        unordered_set<int> dict(to_delete.begin(),to_delete.end());//将要删除的vector变成哈希集,方便查找
        root=helper(root,dict);
        //由于调用helper之后root本身可能被删除了,所以如果root还在,其本身的那棵树也是答案的一部分
        if(root)
        {
            ans.emplace_back(root);
        }
        return ans;
    }
    TreeNode* helper(TreeNode* root,unordered_set<int>& dict)
    {
        if(root==nullptr) return root;
        //这里要使用后续遍历,因为要保证优先遍历到所有节点再删除,如果采用前序或者中序遍历,遍历的过程中,万一将其删除了,后续没有办法继续遍历被删除节点的子节点了
        root->left=helper(root->left,dict);
        root->right=helper(root->right,dict);
        if(dict.find(root->val)!=dict.end())//找到要删除的节点,将其左右子树加入ans即可(注意这里的左右子树已经被删除过了),随后当前节点置空不会影响后续遍历,因为已经是后序遍历了
        {
            if(root->left) ans.emplace_back(root->left);
            if(root->right) ans.emplace_back(root->right);
            root=nullptr;
        }
        return root;
    }
};

路径问题

一般路径:
vector<vector<int>>res;
void dfs(TreeNode*root,vector<int>path)
{
    if(!root) return;  //根节点为空直接返回
    path.push_back(root->val);  //作出选择
    if(!root->left && !root->right) //如果到叶节点  
    {
        res.push_back(path);
        return;
    }
    dfs(root->left,path);  //继续递归
    dfs(root->right,path);
}

# **给定和的路径:**
void dfs(TreeNode*root, int sum, vector<int> path)
{
    if (!root)
        return;
    sum -= root->val;
    path.push_back(root->val);
    if (!root->left && !root->right && sum == 0)
    {
        res.push_back(path);
        return;
    }
    dfs(root->left, sum, path);
    dfs(root->right, sum, path);
}

作者:eh-xing-qing
链接:https://leetcode-cn.com/problems/path-sum-iii/solution/yi-pian-wen-zhang-jie-jue-suo-you-er-cha-smch/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

257. 二叉树的所有路径

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<string> ans;
    vector<string> binaryTreePaths(TreeNode* root) 
    {  
        dfs(root,"");
        return ans;
    }
    void dfs(TreeNode* root,string path)
    {
        if(!root) return;
        path+=to_string(root->val);
        
        if(!root->left &&!root->right)//碰到叶子节点
        {
            ans.push_back(path);
            return;
        }
        dfs(root->left,path+"->");//继续递归
        dfs(root->right,path+"->");

    }
};

路径和1:112. 是否存在(根到叶子)问题

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) 
    {
        if(!root) return false;
        if(!root->left &&!root->right)//到叶子节点
        {
            return targetSum==root->val;

        }
        return hasPathSum(root->left,targetSum-root->val) ||hasPathSum(root->right,targetSum-root->val) ;//继续递归左右

    }
};

路径和2:113:输出路径(根到叶子)问题

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> ans;
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) 
    {
        
        vector<int> path;
        dfs(root,targetSum,path);
        return ans;
    }
    void dfs(TreeNode* root, int targetSum,vector<int> path)
    {
        if(!root) return;
        targetSum-=root->val;
        path.push_back(root->val);
        if(!root->left &&!root->right)
        {
            if(targetSum==0)
            {
                ans.push_back(path);
                return;
            }
        }
        dfs(root->left,targetSum,path);
        dfs(root->right,targetSum,path);
    }
};

路径和3:437.路径总和等于目标和的数目

注意这里没有要求找到叶子节点,所以找到一条路径之后不能return,接着找
在这里插入图片描述

/**
 1. Definition for a binary tree node.
 2. struct TreeNode {
 3.     int val;
 4.     TreeNode *left;
 5.     TreeNode *right;
 6.     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 7.     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 8.     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 9. };
 */
class Solution {
public:
    int count=0;
    int pathSum(TreeNode* root, int targetSum) 
    {
        if(!root) return 0;
        dfs(root,targetSum);//对于每个节点,分两步,1:遍历以当前节点为头的路径有没有符合条件的;2:遍历以左右节点为头节点。。
        pathSum(root->left,targetSum);
        pathSum(root->right,targetSum);
        return count;   
    }
   void dfs(TreeNode* root, int sum)
    {
       if(!root) return;
       sum-=root->val;
       if(sum==0) ++count;
       dfs(root->left,sum);
       dfs(root->right,sum);
    }
};

对称判断

101.对称二叉树

四步法:

  1. 子树都为空,对称
  2. 只有一个空,必不对称
  3. 值不等,不对称
  4. 递归

详细来说,我们每次先判断根节点是不是空,如果是空就直接返回true。否则判断其左右子树。
对于每个节点的左右子树,我们判度其左右空的情况和值不等的情况,如果都符合就继续判度左的右和右的左以及左的左和右的右这两个节点的情况。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution 
{

public:
    bool isSymmetric(TreeNode* root) 
    {
        return root?  isSymmetric(root->left,root->right):true;
    }
    
    bool isSymmetric(TreeNode* left,TreeNode* right)
    {
        if(!left &&!right) return true;
        if(!left ||!right) return false;
        if(left->val!=right->val) return false;
        return isSymmetric(left->left,right->right) && isSymmetric(left->right,right->left);
    }
};

572. 另一棵树的子树

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool issame(TreeNode* root,TreeNode* subRoot)
    {
      if(root==NULL &&subRoot==NULL) return true;
       if(root==NULL || subRoot==NULL ||root->val!=subRoot->val ) return false;
       return issame(root->left,subRoot->left) && issame(root->right,subRoot->right);

    }
    bool isSubtree(TreeNode* root, TreeNode* subRoot) 
    {
       if(root==NULL &&subRoot==NULL) return true;
       if(root==NULL || subRoot==NULL) return false;
       if(root->val==subRoot->val && issame(root,subRoot)) return true;
       return isSubtree(root->left,subRoot) ||isSubtree(root->right,subRoot);
    }

};

层次遍历

层次遍历主要使用广度优先搜索。二叉树的层次遍历主要用到队列。

637.二叉树每层平均值

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root)
    {
        vector<double> ans;
        if(!root) return ans;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty())//对于每一层
        {
            int count=q.size();//获取当前层有多少个节点
            double sum=0;
            for(int i=0;i<count;++i)
            {
                sum+=q.front()->val;//每次取出最左边的节点,累加
                if(q.front()->left)//然后将其左右节点加入队列
                {
                    q.push(q.front()->left);
                }
                if(q.front()->right)
                {
                    q.push(q.front()->right);
                }
                q.pop();
            
            }
            ans.push_back(sum/count);

        }
        return ans;

    }
};

513. 找树左下角的值

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        int ans;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty())
        {
            ans=q.front()->val;
            int num=q.size();
            for(int i=0;i<num;++i)
            {
                TreeNode* node=q.front();
                q.pop();
                if(node->left)
                {
                    q.push(node->left);
                }
                if(node->right)
                {
                    q.push(node->right);
                }
            }
        }
        return ans;

    }
};

按序遍历

前中后表示在第几步访问根节点。前序表示在第一步访问,中序表示在第二步访问,后序则表示在第三步访问。
在这里插入图片描述

前(先)序遍历:
1)访问根节点
2)先序遍历左子树
3)先序遍历右子树

void preorder(TreeNode* root) 
{
	visit(root);
	preorder(root->left);
	preorder(root->right);
}

所以对于上图二叉树,我们先访问1,然后要先序遍历左子树,访问2,然后先序遍历左子树,访问4,返回上一层遍历其右子树5,返回上一层遍历其右子树3,最后访问6.
所以前序遍历顺序为1-2-4-5-3-6

性质:
前序遍历的第一个节点为根节点。

中序遍历:
1)中序遍历左子树
2)访问根节点
3)中序遍历右子树

void inorder(TreeNode* root) 
{
	inorder(root->left);
	visit(root);
	inorder(root->right);
}

所以我们先左左到底,访问到4,然后访问其根节点2,然后访问右子树5,回头访问根节点1,对于其右子树,左为空则返回访问根节点3,最后访问其右子树6.
所以中序遍历顺序为4-2-5-1-3-6
性质:1.中序遍历的第一个节点为左下节点。2.中序遍历的根节点左边是左子树,右边是右子树
后序遍历
1)后续遍历左子树
2)后续遍历右子树
3)访问根节点

void postorder(TreeNode* root) 
{
	postorder(root->left);
	postorder(root->right);
	visit(root);
}

4-5-2-6-3-1
性质:1.后续遍历的第一个节点也为左下节点.2.后续遍历的最后一个节点为根节点

144.二叉树前序遍历

递归:

class Solution {
public:
    vector<int> ans;
    vector<int> preorderTraversal(TreeNode* root) 
    {
        
        helper(root,ans);
        return ans;
    }
    void helper(TreeNode* root,vector<int> &ans)
    {
        if(!root) return;
        ans.push_back(root->val);
        helper(root->left,ans);
        helper(root->right,ans);
    }
};

迭代:
我们也可以用迭代的方式实现方法一的递归函数,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同,具体可以参考下面的代码。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) 
    {
        vector<int> ans;
        if(!root) return ans;
        stack<TreeNode*> s;
        s.push(root);
        while(!s.empty())
        {
            TreeNode* node=s.top();
            s.pop();//不同于层次遍历,这里我们使用栈的话得及时弹出
            ans.push_back(node->val);
            if(node->right)//右子树先入栈后处理。
            {
                s.push(node->right);
            }
             if(node->left)
            {
                s.push(node->left);
            }
        }
        return ans;
    }
  
};

为了保持前中后序遍历代码风格统一:我们稍微改写一下上述代码:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) 
    {
        vector<int> ans;
        if(root==nullptr) return ans;
        stack<TreeNode*> s;
        while(root ||!s.empty())
        {
            while(root)
            {
                ans.push_back(root->val);
                s.push(root);
                root=root->left;
            }
            root=s.top();
            s.pop();
            root=root->right;
        }
        return ans;

    }
    
};

94. 二叉树的中序遍历(重点)

递归:

class Solution {
public:
    vector<int> ans;
    vector<int> inorderTraversal(TreeNode* root) 
    {
        if(!root) return ans;
        helper(root);
        return ans;

    }
    void helper(TreeNode* root)
    {
        if(!root) return;
        helper(root->left);
        ans.push_back(root->val);
        helper(root->right);
    }
};

迭代:

class Solution {
public:
    
    vector<int> inorderTraversal(TreeNode* root) 
    {
        vector<int> ans;
        if(!root) return ans;
        stack<TreeNode*> s;
        while(root|| !s.empty())
        {
            while(root)//左臂入栈
            {
                s.push(root);
                root=root->left;
            }
            root=s.top();
            s.pop();
            ans.push_back(root->val);
            root=root->right;//按照中序遍历,左读右的顺序,处理完前两部要处理右子树
        }
        return ans;

    }

};

145. 二叉树的后序遍历

递归:

class Solution {
public:
    vector<int> ans;
    vector<int> postorderTraversal(TreeNode* root) 
    {
        if(!root) return ans;
        helper(root);
        return ans;
    }
     void helper(TreeNode* root)
    {
        if(!root) return;
        helper(root->left);
        helper(root->right);
        ans.push_back(root->val);
    }
};

迭代:
对于后序遍历,唯一的特殊点是需要多一个pre来记录上一次访问的节点,避免重复访问。
基本思路是这样的
只要root和栈有一个非空,进行如下循环:
1.左到底
2.取出栈顶元素,看其有没有右子树,这里分两种情况
2.1如果有右子树且未访问过,右移,返回上一步
2.2else:即如果没有右子树(其是叶子节点)或者右子树已经被遍历过,则访问,对于下一个节点来说,当前节点就是pre,pre=root,访问完了要将root置空防止重复访问

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) 
    {
        vector<int> ans;
        if(root==nullptr) return ans;
        stack<TreeNode*> s;
        TreeNode* pre=nullptr;
        while(root ||!s.empty())
        {
            while(root)//左遍历到底
            {
                s.push(root);
                root=root->left;
            }
            root=s.top();
            
            if(root->right!=nullptr && root->right!=pre)//说明其右子树还在且未被访问
            {
                root=root->right;
            }
            else if(root->right == nullptr ||root->right==pre)//如果其是叶子节点(右子树为空)或者其右子树已经遍历过了,这里其实用else即可,我写出来是为了方便大家理解
            {
                ans.push_back(root->val);
                s.pop();
                pre=root;
                root=nullptr;//避免重复访问当前节点
            }
        }
        return ans;
    }
    
};

105.从前序和中序遍历序列构造二叉树

在这里插入图片描述
我们利用上述性质来解决这个问题:
首先前序遍历的第一个节点为3,代表这棵树根节点为3.
中序遍历的第一个节点为9,代表9在3的最左下端。且中序遍历9后面就是3,则3的左子树只有9.
总之,在中序遍历中,根节点的左边为左子树9,右边为右子树,节点数目为3.

在中序遍历中对根节点进行定位时,一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。我们可以考虑使用哈希表来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键表示一个元素(节点的值),值表示其在中序遍历中的出现位置。在构造二叉树的过程之前,我们可以对中序遍历的列表进行一遍扫描,就可以构造出这个哈希映射。在此后构造二叉树的过程中,我们就只需要 O(1)O(1) 的时间对根节点进行定位了。

假设构造哈希表hp,我们有hp[3]=1.意思就是根节点在中序遍历中的坐标定位为1.
那么我们就可以根据中序遍历的结构【左,根,右】来计算左右子树的size。
左子树的size为1-0=1,那么右子树的size为n-1-1
然后我们构造根节点,递归构造左右子树。

对于左子树:
其范围为在前序中为:
左边界+1到左边界+左size
其范围在中序中为:
左边界到根节点左边一位

右子树同理。
这题的官方题解参数虽然很多,但是看懂之后会异常明了,值得学习。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
private:
    unordered_map<int,int> mp;
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) 
    {
        //前序遍历的第一个节点为根节点
        //中序遍历的第一个节点为左下角节点,我们找到中序遍历的根节点3,左边1个数就是根节点的左子树,右边3个数就是根节点的右子树。
        //使用哈希表,定位每个节点在中序遍历中的位置,用来通过前序3找到中序3的坐标1
        int n=inorder.size();
        for(int i=0;i<n;++i)
        {
            mp[inorder[i]]=i;
        }
        return helper(preorder,inorder,0,n-1,0,n-1);


    }
    TreeNode* helper(vector<int>& preorder, vector<int>& inorder,int preorder_left,int preorder_right,int inorder_left,int inorder_right)
    {
        if(preorder_left>preorder_right) return nullptr;
        //确定根节点在前序中的坐标为0
        int preorder_root=preorder_left;
        //根据前序中的坐标,通过哈希映射,确定根节点在中序遍历中的坐标,mp[3]=1
        int inorder_root=mp[preorder[preorder_root]];
        //以3为val建立根节点
        TreeNode* root=new TreeNode(preorder[preorder_root]);
        //确定左子树大小为1
        int sizeof_lefttree=inorder_root-inorder_left;
        //递归构造左右子树,在先序遍历中节点位置为【根,左,右】,而中序遍历为【左,根,右】,以此为标准确定左右子树范围

        //递归构造左子树,并用root->left链接
        //在先序遍历中:左子树为preorder_left+1开始,直到preorder_left+sizeof_lefttree结束
        //在中序遍历中:左子树为inoder_left一直到inorder_root-1
        root->left=helper(preorder,inorder,preorder_left+1,preorder_left+sizeof_lefttree,inorder_left,inorder_root-1);

        //同理构造右子树,先序遍历中,
        root->right=helper(preorder,inorder,preorder_left+sizeof_lefttree+1,preorder_right,inorder_root+1,inorder_right);
        return root;
    }
};

二叉查找树

二叉查找树(Binary Search Tree, BST)是一种特殊的二叉树:对于每个父节点,其左子节点的值小于等于父结点的值,其右子节点的值大于等于父结点的值
因此对于一个二叉查找树,我们可以在 O(nlogn) 的时间内查找一个值是否存在:从根节点开始,若当前节点的值大于查找值则向左下走,若当前节点的值小于查找值则向右下走。同时
因为二叉查找树是有序的,对其中序遍历的结果为升序的数组。
**反之,中序遍历倒序 (即右根左)为 递减序列 **

template <class T>
class BST
{
	struct node
	{
		T data;
		node* left;
		node* right;
	}
	node* root;
	//清空节点
	node* makempty(node* t)
	{
		if(!t) return NULL;
		makempty(t->left);
		makempty(t->right);
		delete t;
		return NULL;
	}
	//插入
	node* insert(node *t,T x)
	{
		if(!t) //如果是空节点,则开辟一个值为空的节点左右指向空即可
		{
		t=new node;
		t->data=x;
		t->left=t->right=NULL;
		}
		else if(x<t->data)//如果其小于当前节点则要往左下移
		{
			t->left=insert(t->left,x);
		}
		else if(x>t->data)//如果其大于
		{
			t->right=insert(t->right,x);
		}
		return t;
	}

	//查找O(nlogn)
	node* find(node* t,T x)
	{
		if(!t) return NULL;
		if(x<t->data) return find(t->left,x);
		else if(x>t->data) return find(t->right,x);
		return t;
	}
	//查找最小值
	node* findmin(node*t)
	{
		if(!t ||!t->left) return t;
		return find(t->left);
	}
	//查找最大值
	node* findmax(node*t)
	{
		if(!t||!t->right) return t;
		return findmax(t->right);
	}
	//移除某个节点
	node* remove(node*t,T x)
	{
		node* temp;
		if(!t) return NULL;
		else if(x<t->data) t->left=remove(t->left,x);//大了或者小了,还没找到的情况
		else if(x>t->data) t->right=remove(t->right,x);
		else if(t->left && t->right)//找到了,且有左右孩子的情况,如果把当前节点去掉,那么就得在右孩子找一个最小的来替代他的位置,然后把那个最小值给移除掉
		{
			temp=findmin(t->right);
			t->data=temp->data;
			t->right=remove(t->right,t->data);
		}
		else//找到了,但是是叶子节点或者是单亲家庭(只有一个孩子)
		{
			temp=t;
			if(!t->left)//左边为空
			t=t->right;
			else if(!t->right)//右边为空
			t=t->left;
			delete temp;
		}
		return t;
	}
public: 
BST():root(NULL){}
~BST()//析构函数
{
	root=makempty(root);
}	
void insert(T x)//重载
{insert(root,x);}
void remove(T x)
{remove(root,x);}
	
};

669.修剪二叉搜索树

在这里插入图片描述

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) 
    {
        if(!root) return root;
        if(root->val>high)//如果根节点已经超过上界,那么其右边所有子树必定超出上届,返回其左子树即可
        {
            return trimBST(root->left,low,high);
        }
        if(root->val<low)//如果根节点已经小于下届,那么左边子树肯定都小
        {
            return trimBST(root->right,low,high);

        }
        //如果值符合要求,那么递归处理其左右子树
        root->left=trimBST(root->left,low,high);
        root->right=trimBST(root->right,low,high);
        return root;

    }
};

99. 恢复二叉搜索树

在这里插入图片描述
**对于二叉搜索树,我们知道如果对其进行中序遍历,得到的值序列是递增有序的,而如果我们错误地交换了两个节点,等价于在这个值序列中交换了两个值,破坏了值序列的递增性。
**
我们只要找到两个节点,然后把他们的val交换即可
可以用中序遍历这个树,然后设置一个pre指针,记录当前节点的中序遍历上一个节点。
在遍历过程中,如果只出现了一次顺序错误,说明两个节点挨着的,相邻节点交换即可。
如果出现两次,则交换这两个节点即可。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode *mistake1=nullptr,*mistake2=nullptr,*pre=nullptr;
    void recoverTree(TreeNode* root) 
    {
        
        inorder(root);
        if(mistake1&& mistake2)
        {
            swap(mistake1->val,mistake2->val);
        }
    }
    void inorder(TreeNode* root)
    {
        if(!root) return;
        if(root->left) 
        {
            inorder(root->left);
        }
        if(pre && pre->val > root->val)
        //这里注意,左下角的才是pre,而右上角的是root,一般来说左下应该小,即pre<root,所以只要pre>root即表示错误。
        //而对于另一种情况,左上是pre,右下是root,一般来说应该右下大,即pre<root,所以两种情况都是pre>root即表示错误。
        //出现错误之后,如果mistake1是空,代表是第一次错误,这时记录相邻两个点(如果到最后也只有一次代表这两个数就是相邻的);如果非空,代表出现了两次错误,出现两次错误交换两个不相邻的点
        {
            if(!mistake1)
            {
                mistake1=pre;
                mistake2=root;
            }
            else
            {
                mistake2=root;
            }

        }
        pre=root;
        if(root->right)
        {
            inorder(root->right);
        }
    }
};

字典树

字典树(Trie)用于判断字符串是否存在或者是否具有某种字符串前缀。
为什么需要用字典树解决这类问题呢?假如我们有一个储存了近万个单词的字典,即使我们使用哈希,在其中搜索一个单词的实际开销也是非常大的,且无法轻易支持搜索单词前缀。然而由于一个英文单词的长度 n 通常在 10 以内,如果我们使用字典树,则可以在 O(n)——近似 O(1)的时间内完成搜索,且额外开销非常小。
意思就是用树的层数,换哈希表的查找时间。

class Trie {
    bool isval;
    Trie* next[26];
public:
    /** Initialize your data structure here. */
    Trie(){
        isval=false;
        memset(next,0,sizeof(next));
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) 
    {
        //先从根结点的子结点开始与 word 第一个字符进行匹配,一直匹配到前缀链上没有对应的字符,这时开始不断开辟新的结点,直到插入完 word 的最后一个字符,同时还要将最后一个结点isEnd = true;,表示它是一个单词的末尾。
        Trie* node=this;
        for(char c:word)
        {
            if(node->next[c-'a']==nullptr)//如果之前没有,要开辟新的节点
            {
                node->next[c-'a']=new Trie();
            }
            node=node->next[c-'a'];//匹配
        }
        node->isval=true;//最后一个节点为末尾
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        Trie* node=this;
        for(char c:word)
        {
            node=node->next[c-'a'];
            if(node==nullptr) return false;
        }
        return node->isval;//注意字典树的末尾才存放单词,中途符合是不算的
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) //这个只要中途匹配就可
    {
        Trie *node =this;
        for(char c:prefix)
        {
            node=node->next[c-'a'];
            if(node==nullptr)
                return false;
        }
        return true;

    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

练习

226.翻转二叉树

用前后序遍历,翻转每个节点的左右孩子,然后递归翻转孩子的左右孩子即可。

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(!root) return root;
        swap(root->left,root->right);//翻转左右孩子
        root->left=invertTree(root->left);//递归翻转左右孩子的左右孩子
        root->right=invertTree(root->right);
        return root;
    }
};

617. 合并二叉树

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) 
    {
        if(!root1) return root2;//如果有一边是空,则该节点就是另一个节点即可
        if(!root2) return root1;
        if(root1 && root2) 
            root1->val+=root2->val;
        root1->left=mergeTrees(root1->left,root2->left);
        root1->right=mergeTrees(root1->right,root2->right);
        return root1;
        
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值