C++算法学习五.二叉树(1)

1.二叉树理论基础

二叉树的种类:

满二叉树:一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。深度为k,总共有2的k次幂-1个节点。

完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。

二叉搜索树:二叉搜索树是一个有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

 平衡二叉搜索树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,map、set的增删操作时间时间复杂度是logn。

二叉树的存储方式:链式存储(指针)(连续)或者顺序存储(数组)(节点串联)

链式存储需要有节点元素,左指针(左孩子)和右指针(右孩子),左指针需要指向左边的子树,右指针指向右边的子树,根据节点的指针来实现的(一般采用的)

顺序存储:数组存储二叉树,数组更像是层序遍历之后的元素集合,如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

二叉树的遍历方式:

深度优先遍历:先往深走,遇到叶子节点再往回走。

前序遍历(递归法,迭代法)

中序遍历(递归法,迭代法)

后序遍历(递归法,迭代法)

广度优先遍历:一层一层的去遍历。

层次遍历(迭代法)

 这两种遍历是图论中最基本的两种遍历方式。

前序遍历:中左右

中序遍历:左中右

后序遍历:左右中

这里前中后指的是中间节点的位置,左指的是左子树,右指的是右子树(子树也要实现相应的遍历顺序)

栈其实就是递归的一种实现结构,深度优先遍历可以使用栈来迭代完成,广度优先遍历一般使用队列迭代实现完成,

二叉树的定义:(链式存储)

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

其实和链表的定义方式差不多,比链表多一个指针,指向左孩子,右孩子 。

2.二叉树的递归遍历

递归做法三部曲:

  1. 确定递归函数的参数和返回值:我们要传进来什么样的参数
  2. 确定终止条件:确定什么时候递归结束,避免栈错误出现
  3. 确定单层递归逻辑:确认每一层递归需要处理的信息

例如leetcode上的二叉树的前序遍历(144题),使用递归三部曲来解决 ,

class Solution {
public://用递归方法实现的前序遍历
    void traversal(TreeNode* cur,vector<int>& vec){//三部曲1.确定参数和返回值,需要传入节点和数组,
        if(cur == NULL){//三部曲2.确定终止条件,遇到叶片节点返回
            return;
        }
        //三部曲3.确定单层递归的逻辑
        vec.push_back(cur->val);//中间节点
        traversal(cur->left,vec);//左子树
        traversal(cur->right,vec);//右子树
    }
    vector<int> preorderTraversal(TreeNode* root) {//实现的函数
        vector<int>result;//定义一个数组去接收遍历的元素
        traversal(root,result);//我们需要把根节点,和数组传入函数即可实现
        return result;
    }
};

接下来是二叉树的后序遍历(145题)

class Solution {
public:
//用递归方法实现的后序遍历
    //三部曲1.确定参数和返回值,需要传入节点和数组,
    void traversal(TreeNode* cur,vector<int>& vec){
        //三部曲2.确定终止条件,遇到叶片节点返回
        if(cur == NULL){
            return;
        }
        //三部曲3.确定单层递归的逻辑
        traversal(cur->left,vec);//左子树
        traversal(cur->right,vec);//右子树
        vec.push_back(cur->val);//中间节点
    }
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int>result;//定义一个数组去接收遍历的元素
        traversal(root,result);//我们需要把根节点,和数组传入函数即可实现
        return result;
    }
};

下面是二叉树的中序遍历(94题):

class Solution {
public:
//用递归方法实现的中序遍历
    //三部曲1.确定参数和返回值,需要传入节点和数组,
    void traversal(TreeNode* cur,vector<int>& vec){
        //三部曲2.确定终止条件,遇到叶片节点返回
        if(cur == NULL){
            return;
        }
        //三部曲3.确定单层递归的逻辑
        traversal(cur->left,vec);//左子树
        vec.push_back(cur->val);//中间节点
        traversal(cur->right,vec);//右子树
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int>result;//定义一个数组去接收遍历的元素
        traversal(root,result);//我们需要把根节点,和数组传入函数即可实现
        return result;
    }
};

3.二叉树的迭代遍历

递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,所以我们采用栈来实现递归的二叉树前中后序遍历。

前序遍历:迭代法,定义一个栈,先将根节点传进去,注意栈的类型,直到栈为空,先将右节点放进去,在把左节点放进去。因为栈的特性先进后出,前序遍历是中左右的顺序,所以需要先把右子树根节点压进去,之后在处理,再把左子树根节点压入处理。 

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*>st;//定义一个栈去实现递归操作,指定树指针
        vector<int>result;//结果数组集
        if(root == NULL)return result;//如果根节点为空,直接返回
        st.push(root);//把根节点压入栈中
        //栈里的元素没空就继续执行知道全为空为止
        while(!st.empty()){
            TreeNode* node = st.top();//树节点就是栈顶元素
            st.pop();//弹出栈顶元素
            result.push_back(node->val);//放到结果数组当中
            if(node->right)st.push(node->right);//先放入右节点,因为弹出的时候是先进后出
            if(node->left)st.push(node->left);//先处理左节点,
        }
        return result;
    }
};

后序遍历:迭代法,也是需要定义一个栈,因为前序遍历是中左右,后序遍历的顺序是左右中,我们将顺序调换,中右左,在反转一下数组即可得到后序遍历,

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*>st;//定义一个栈,注意栈里数据类型,是树的指针
        vector<int>result;//结果
        if(root == NULL)return result;//注意要判断
        st.push(root);//先将根节点指针压入,建立联系
        //循环语句处理,栈不为空就处理
        while(!st.empty()){
            //栈顶元素定义当前指针,
            TreeNode* node = st.top();
            st.pop();//然后弹出指针
            result.push_back(node->val);//将这个值放入数组
            if(node->left)st.push(node->left);//注:先放左子树,后方右子树
            if(node->right)st.push(node->right);
        }
        reverse(result.begin(),result.end());//反转一下数组即可
        return result;
    }
};

中序遍历:迭代法,之前前序后序遍历都是先处理元素放入数组,在遍历节点,访问元素和处理元素顺序一致中间节点,中序遍历是左中右的顺序,先访问根节点,在一层一层向下访问,到达最左面叶片节点,处理节点,

中序遍历需要指针遍历来访问节点,栈来处理元素

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*>st;//定义一个栈,
        vector<int>result;//结果
        //if(root == NULL)return result;
        TreeNode* cur = root;//当前指针是用来访问最左侧的叶片节点,指针指向根节点
        //循环条件,栈不为空,
        while(cur!=NULL||!st.empty()){
            //访问最左侧的叶片节点
            if(cur!=NULL){
                st.push(cur);//将访问的节点放入栈中
                cur = cur->left;//找左
            }else{
                cur = st.top();//放入元素,栈顶
                st.pop();//弹出
                result.push_back(cur->val);//把这个值放入结果,且现在中间节点
                cur = cur->right;//访问右侧节点
            }
        }
        return result;
    }
};

前序后序节点改变一下顺序就可以实现,中序遍历不可以 

4.二叉树的统一迭代法

将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 

 中序遍历使用统一迭代法来实现:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*>st;
        vector<int>result;
        if(root != NULL)st.push(root);//加入根节点
        //循环条件
        while(!st.empty()){
            TreeNode* node = st.top();//指针指向根节点
            //节点不为空就加入,遇到空节点加入结果集
            if(node != NULL){
                st.pop();//避免多操作
                if(node->right)st.push(node->right);//右
                st.push(node);//中
                st.push(NULL);//访问过没做处理使用null来做标记
                if(node->left)st.push(node->left);//左
            }else{
                st.pop();//空节点弹出
                node = st.top();//取出栈元素
                st.pop();
                result.push_back(node->val);//加入结果集
            }
        }
        return result;
    }
};

 前序遍历使用统一迭代法来实现:只是顺序部分调换一下即可实现,

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*>st;
        vector<int>result;
        if(root != NULL)st.push(root);//加入根节点
        //循环条件
        while(!st.empty()){
            TreeNode* node = st.top();//指针指向根节点
            //节点不为空就加入,遇到空节点加入结果集
            if(node != NULL){
                st.pop();//避免多操作
                if(node->right)st.push(node->right);//右
                if(node->left)st.push(node->left);//左
                st.push(node);//中
                st.push(NULL);//访问过没做处理使用null来做标记
            }else{
                st.pop();//空节点弹出
                node = st.top();//取出栈元素
                st.pop();
                result.push_back(node->val);//加入结果集
            }
        }
        return result;
    }
};

后序遍历使用统一迭代法来实现:只是顺序部分调换一下即可实现

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*>st;
        vector<int>result;
        if(root != NULL)st.push(root);//加入根节点
        //循环条件
        while(!st.empty()){
            TreeNode* node = st.top();//指针指向根节点
            //节点不为空就加入,遇到空节点加入结果集
            if(node != NULL){
                st.pop();//避免多操作
                st.push(node);//中
                st.push(NULL);//访问过没做处理使用null来做标记
                if(node->right)st.push(node->right);//右
                if(node->left)st.push(node->left);//左
            }else{
                st.pop();//空节点弹出
                node = st.top();//取出栈元素
                st.pop();
                result.push_back(node->val);//加入结果集
            }
        }
        return result;
    }
};

5.二叉树层序遍历

二叉树层序遍历(102题)

题目描述:给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。层序就是图中的广度优先遍历,(迭代法)

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*>que;//定义一个队列,去迭代
        if(root != NULL)que.push(root);//如果根节点不为0,将根节点压入队列
        vector<vector<int>>result;//结果集
        //循环条件队列不为空
        while(!que.empty()){
            int size = que.size();//定义队列的大小
            vector<int>vec;//每层数组接收
            //从队列里遍历元素
            for(int i = 0;i < size;i++){
                TreeNode* node = que.front();//队头元素是节点
                que.pop();//弹出
                vec.push_back(node->val);//传入数组
                if(node->left)que.push(node->left);//左子树压入队列
                if(node->right)que.push(node->right);//右子树压入队列
            }
            result.push_back(vec);//每层数组压入数组中
        }
        return result;
    }
};

递归法:

class Solution {
public:
    void order(TreeNode* cur, vector<vector<int>>& result, int depth)
    {
        if (cur == nullptr) return;
        if (result.size() == depth) result.push_back(vector<int>());
        result[depth].push_back(cur->val);
        order(cur->left, result, depth + 1);
        order(cur->right, result, depth + 1);
    }
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> result;
        int depth = 0;
        order(root, result, depth);
        return result;
    }
};

记住size是不可缺少的,因为que.size()在不断地变化 

二叉树的层次遍历 II(107题)

题目描述:给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]

思路:类似上提,最后反转数组即可,

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        queue<TreeNode*>que;//定义一个队列,去迭代
        if(root != NULL)que.push(root);//如果根节点不为0,将根节点压入队列
        vector<vector<int>>result;//结果集
        //循环条件队列不为空
        while(!que.empty()){
            int size = que.size();//定义队列的大小
            vector<int>vec;//每层数组接收
            //从队列里遍历元素
            for(int i = 0;i < size;i++){
                TreeNode* node = que.front();//队头元素是节点
                que.pop();//弹出
                vec.push_back(node->val);//传入数组
                if(node->left)que.push(node->left);//左子树压入队列
                if(node->right)que.push(node->right);//右子树压入队列
            }
            result.push_back(vec);//每层数组压入数组中
        }
        reverse(result.begin(),result.end());//反转即可
        return result;
    }
};

二叉树的右视图 (199题)

题目描述:给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

思路:层序遍历,每次取队列尾部的值即可,层序遍历每次取最后队列尾部元素即可

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        queue<TreeNode*> que;//定义一个队列
        vector<int> result;//结果
        if(root != NULL)que.push(root);//根指针不为空
        //循环条件
        while (!que.empty()) {
            int size = que.size();//需要预存队列大小
            //队列里遍历元素
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();//当前指针指向队头元素
                que.pop();//弹出
                if(i == (size - 1))result.push_back(node->val);//取最后队尾元素,放到结果集
                if(node->left)que.push(node->left);//左孩子
                if(node->right)que.push(node->right);//右孩子
            }
        }
        return result;
    }
};

二叉树的层平均值(637题)

题目描述:给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。

思路:层序遍历, 把一层求个总和在取一个均值

class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        queue<TreeNode*>que;//定义队列程序遍历
        vector<double>result;//结果,注意数据类型
        if(root != NULL)que.push(root);//不为空指针
        while(!que.empty()){
            int size = que.size();//注意预存队列大小
            double sum = 0;//要定义一个和值
            for(int i = 0;i < size;i++){
                TreeNode* node = que.front();
                que.pop();
                sum += node->val;//计算每层的和
                if(node->left)que.push(node->left);
                if(node->right)que.push(node->right);
            }
            result.push_back(sum/size);//我们把均值加入结果集中
        }
        return result;
    }
};

 N叉树的层序遍历(429题)

题目描述:给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。

思路:层序遍历,就是孩子多了而已

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        queue<Node*>que;
        vector<vector<int>>result;
        if(root != NULL)que.push(root);
        while(!que.empty()){
            int size = que.size();
            vector<int>vec;
            for(int i = 0;i < size;i++){
                Node* node = que.front();
                que.pop();
                vec.push_back(node->val);
                //遍历多个子孩子节点
                for(int j = 0;j < node->children.size();j++){
                    if(node->children[j])que.push(node->children[j]);
                }
            }
            result.push_back(vec);
        }
        return result;
    }
};

 在每个树行中找最大值(515题)

题目描述:您需要在二叉树的每一行中找到最大的值。

思路:层序遍历,找每一行的最大值即可 

class Solution {
public:
    vector<int> largestValues(TreeNode* root) {
        queue<TreeNode*>que;
        vector<int>result;
        if(root != NULL)que.push(root);
        while(!que.empty()){
            int size = que.size();
            int maxvalue = INT_MIN;
            for(int i = 0;i < size;i++){
                TreeNode* node = que.front();
                que.pop();
                maxvalue = node->val > maxvalue ? node->val : maxvalue;//每一行取最大值
                if(node->left)que.push(node->left);
                if(node->right)que.push(node->right);
            }
            result.push_back(maxvalue);
        }
        return result;
    }
};

 填充每个节点的下一个右侧节点指针(116题)

题目描述:

给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。

初始状态下,所有 next 指针都被设置为 NULL。

思路:迭代法(层序遍历) 在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了

class Solution {
public:
    Node* connect(Node* root) {
        queue<Node*> que;
        if (root != NULL)
            que.push(root);
        while (!que.empty()) {
            int size = que.size();
            Node* nodePre;
            Node* node;
            for (int i = 0; i < size; i++) {
                if (i == 0) {
                    nodePre = que.front(); // 取出一层的头结点
                    que.pop();
                    node = nodePre;
                } else {
                    node = que.front();
                    que.pop();
                    nodePre->next = node; // 本层前一个节点next指向本节点
                    nodePre = nodePre->next;
                }
                if (node->left)
                    que.push(node->left);
                if (node->right)
                    que.push(node->right);
            }
            nodePre->next = NULL; // 本层最后一个节点指向NULL
        }
        return root;
    }
};

 填充每个节点的下一个右侧节点指针II(117题)

题目描述:

思路和上面那个一样只不过题目改成了完全二叉树

class Solution {
public:
    Node* connect(Node* root) {
        queue<Node*>que;
        if(root != NULL)que.push(root);
        while(!que.empty()){
            int size = que.size();
            Node* node;
            Node* nodepre;
            for(int i = 0;i < size;i++){
                if(i == 0){
                    nodepre = que.front();
                    que.pop();
                    node = nodepre;
                }else{
                    node = que.front();
                    que.pop();
                    nodepre->next = node;
                    nodepre = nodepre->next;
                }
                if(node->left)que.push(node->left);
                if(node->right)que.push(node->right);
            }
            nodepre->next = NULL;
        }
        return root;
    }
};

6.翻转二叉树(226题)

思路:反转二叉树把每个节点的左右孩子交换一下即可,遍历过程中反转每个节点左右孩子就可以实现

递归法实现:三部曲:确定返回值和参数:参数主要是树的根节点指针,返回值就是树的节点就好,确定终止条件节点为空就返回,确定单层递归的逻辑:前序遍历,先进行交换左右孩子节点,然后反转左子树,反转右子树

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(root == NULL)return root;//根节点为空直接返回
        swap(root->left,root->right);//前序遍历的方法,中
        invertTree(root->left);//左
        invertTree(root->right);//右
        return root;//最后返回
    }
};

迭代法实现:将中序遍历迭代法实现即可

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        stack<TreeNode*>st;//申请一个栈
        if(root == NULL)return root;//判断
        st.push(root);//栈建立联系
        //循环条件
        while(!st.empty()){
            TreeNode* node = st.top();//节点是栈顶元素
            st.pop();//弹出
            swap(node->left,node->right);//中间处理方式
            if(node->right)st.push(node->right);//右
            if(node->left)st.push(node->left);//左
        }
        return root;
    }
};

层序遍历:层序遍历也可以把每个节点的左右孩子都翻转一遍

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        queue<TreeNode*> que;//用队列层序遍历
        if (root == NULL)
            return root;
        que.push(root);//队列和树联系
        while (!que.empty()) {
            int size = que.size();//队列大小需要记录
            //每层去迭代
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();//队头元素是需要处理的节点
                que.pop();//弹出
                swap(node->left, node->right);//需要处理的逻辑
                if(node->left)que.push(node->left);//左
                if(node->right)que.push(node->right);//右
            }
        }
        return root;
    }
};

7.对称二叉树(101题)

题目描述:给定一个二叉树,检查它是否是镜像对称的。

输入:root = [1,2,2,3,4,4,3]
输出:true

思路:我们需要比较根节点的左子树与右子树是不是相互翻转的,其实我们要比较的是两个树(这两个树是根节点的左右子树),比较的是两个子树的里侧和外侧的元素是否相等。遍历只能是“后序遍历”,要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等,遍历两棵树而且要比较内侧和外侧节点,是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。

递归写法:确定函数返回值和参数,终止条件,单层递归逻辑

class Solution {
public:
    //递归三部曲首先确定返回值和参数参数是左右子树
    bool compare(TreeNode* left,TreeNode* right){
        if(left == NULL && right != NULL)return false;//左节点为空,右节点不空
        else if(left != NULL && right == NULL)return false;//左节点不空,右节点为空
        else if(left == NULL && right == NULL)return true;//都为空
        else if(left->val != right->val)return false;//都不为空,但是数值不等
        bool outside = compare(left->left,right->right);//单层递归逻辑,左子树的左节点和右子树的右节点
        bool inside = compare(left->right,right->left);//左子树的右节点和右子树的左节点采用后序遍历
        bool issame = outside && inside;//中
        return issame;
    }
    bool isSymmetric(TreeNode* root) {
        if(root == NULL)return false;//不对空指针操作
        return compare(root->left,root->right);
    }
};

迭代法:(队列方法实现)

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        queue<TreeNode*>que;//定义一个队列
        if(root == NULL)return true;//如果为空,返回正确
        que.push(root->left);//我们把根节点的左子树和右子树的根节点加入队列
        que.push(root->right);
        //循环条件
        while(!que.empty()){
            TreeNode* leftnode = que.front();//左节点指针和右节点指针
            que.pop();
            TreeNode* rightnode = que.front();
            que.pop();
            //左右都为空继续上述操作
            if(!leftnode && !rightnode){
                continue;
            }
            //左子树和右子树一个不为空或者节点的值不相等返回错
            if(!leftnode || !rightnode || leftnode->val != rightnode->val){
                return false;
            }
            //我们首先先放左节点的左子树,再放右节点的右子树
            que.push(leftnode->left);
            que.push(rightnode->right);
            //再左节点的右子树,再放右节点的左子树
            que.push(leftnode->right);
            que.push(rightnode->left);
        }
        return true;
    }
};

迭代法(栈实现也可以)

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        stack<TreeNode*>st;//定义一个栈
        if(root == NULL)return true;//如果为空,返回正确
        st.push(root->left);//我们把根节点的左子树和右子树的根节点加入栈
        st.push(root->right);
        //循环条件
        while(!st.empty()){
            TreeNode* leftnode = st.top();//左节点指针和右节点指针
            st.pop();
            TreeNode* rightnode = st.top();
            st.pop();
            //左右都为空继续上述操作
            if(!leftnode && !rightnode){
                continue;
            }
            //左子树和右子树一个不为空或者节点的值不相等返回错
            if(!leftnode || !rightnode || leftnode->val != rightnode->val){
                return false;
            }
            //我们首先先放左节点的左子树,再放右节点的右子树
            st.push(leftnode->left);
            st.push(rightnode->right);
            //再左节点的右子树,再放右节点的左子树
            st.push(leftnode->right);
            st.push(rightnode->left);
        }
        return true;
    }
};

8.二叉树的最大深度(104题)

题目描述:给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

 思路:前序求得深度,后序求得高度,根节点的高度就是二叉树的最大深度,通过后序求的根节点高度来求的二叉树最大深度。

  • 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
  • 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)

递归法:使用后序写的代码

class Solution {
public:
    //递归函数,返回值和参数,深度值和节点
    int getdepth(TreeNode* node){
        if(node == NULL)return 0;//终止条件
        int leftdepth = getdepth(node->left);//左
        int rightdepth = getdepth(node->right);//右
        int depth = 1 + max(leftdepth,rightdepth);//中注意根节点需要加1
        return depth;
    }
    int maxDepth(TreeNode* root) {
        return getdepth(root);//调用递归函数
};

递归法:使用前序遍历(回溯)

class Solution {
public:
    int result;
    //使用前序遍历递归
    void getdepth(TreeNode* node,int depth){
        result = result > depth ? result : depth;//中
        if(node->left == NULL && node->right == NULL)return;//规定终止条件当前节点左右子树都为空就停止
        //左
        if(node->left){
            depth++;
            getdepth(node->left,depth);
            depth--;//回溯
        }
        //右
        if(node->right){
            depth++;
            getdepth(node->right,depth);
            depth--;//回溯
        }
        return ;
    }
    int maxDepth(TreeNode* root) {
        result = 0;
        if(root == NULL)return result;
        getdepth(root,1);
        return result;
    }
};

迭代法:使用层序遍历实现,在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度

class Solution {
public:
    int maxDepth(TreeNode* root) {
        queue<TreeNode*>que;//队列层序遍历
        int result = 0;//结果
        if(root == NULL)return 0;
        que.push(root);
        while(!que.empty()){
            int size = que.size();
            result++;//记录深度
            for(int i = 0;i < size;i++){
                TreeNode* node = que.front();
                que.pop();
                if(node->left)que.push(node->left);
                if(node->right)que.push(node->right);
            }
        }
        return result;
    }
};

n叉树的最大深度 (559题)

题目描述:

给定一个 n 叉树,找到其最大深度。

最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

递归法:和上述思想类似

class Solution {
public:
    int maxDepth(Node* root) {
        if(root == NULL)return 0;//终止条件
        int depth = 0;
        //寻找子树的每一个最大深度
        for(int i = 0;i < root->children.size();i++){
            depth = max(depth,maxDepth(root->children[i]));
        }
        return depth+1;
    }
};

迭代法:

class Solution {
public:
    int maxDepth(Node* root) {
       queue<Node*>que;
       if(root != NULL)que.push(root);
       int depth = 0;
       while(!que.empty()){
           int size = que.size();
           depth++;
           for(int i = 0;i < size;i++){
               Node* node = que.front();
               que.pop();
               for(int j = 0;j < node->children.size();j++){
                   if(node->children[j])que.push(node->children[j]);
               }
           }
       } 
       return depth;
    }
};

9. 二叉树的最小深度(111题)

题目描述:

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

思路 :最小深度是从根节点到最近叶子节点的最短路径上的节点数量。,注意是叶子节点。左右孩子都为空的节点才是叶子节点,

如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。

反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。

 递归法:后序遍历

class Solution {
public:
    //递归算法:使用后序遍历来实现,
    int getdepth(TreeNode* node){
        if(node == NULL)return 0;
        int leftdepth = getdepth(node->left);//左
        int rightdepth = getdepth(node->right);//右
        //左子树为空,右子树不为空,不是最小深度
        if(node->left == NULL && node->right != NULL){
            return 1+rightdepth;
        }
        //右子树为空,左子树不为空,不是最小深度
        if(node->right == NULL && node->left != NULL){
            return 1+leftdepth;
        }
        //和最大深度处理差不多
        int depth = 1+min(leftdepth,rightdepth);
        return depth;
    }
    int minDepth(TreeNode* root) {
        return getdepth(root);
    }
};

迭代法:层序遍历,只有当左右孩子都为空的时候,才说明遍历到最低点了。如果其中一个孩子不为空则不是最低点

class Solution {
public:
    int minDepth(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL)
            que.push(root);
        int depth = 0;
        while (!que.empty()) {
            int size = que.size();
            depth++;
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                if (node->left)
                    que.push(node->left);
                if (node->right)
                    que.push(node->right);
                //注意左右节点都为0就需要退出了
                if (!node->left && !node->right) {
                    return depth;
                }
            }
        }
        return depth;
    }
};

总结:

二叉树理论基础:二叉树的种类:满二叉树(度为0或2)深度为k有2^k-1个节点,完全二叉树保证最左边的全是有节点,优先级队队列是堆,也就是完全二叉树,二叉搜素树是一个有序数左子树小于根节点,右子树大于根节点,左右子树也都是二叉搜索树,平衡二叉搜索树:左右子树的高度不超过1,且也是平衡二叉树,map,set底层都是平衡二叉搜索树,存储方式链式(两个指针,一个根节点)和顺序(数组存储),遍历方式:深度优先遍历前中后序,注意迭代法和递归法的写法,广度优先遍历(层序遍历)前中后指的是中间节点的位置,左指的是左子树,右指的是右子树,深度优先一般使用栈迭代,广度优先使用队列去迭代,注意二叉树的节点结构

递归遍历:三部曲,确定返回值和参数类型,确定终止条件,确定单层递归逻辑,注前中后序的递归代码写法

迭代遍历(前中后序),使用栈去实现,前序后序遍历都是先处理元素放入数组,在遍历节点,访问元素和处理元素顺序一致中间节点,中序遍历是左中右的顺序,先访问根节点,在一层一层向下访问,到达最左面叶片节点,处理节点,中序遍历需要指针遍历来访问节点,栈来处理元素,前序后序节点改变一下顺序就可以实现,中序遍历不可以

二叉树统一迭代方式:将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。

二叉树层序遍历:使用队列来进行迭代,注意代码写法,注需要记录队列大小,因为大小来回变化,在内部用一个for循环遍历元素进行遍历左右子树

翻转二叉树:递归法使用前序遍历交换两个子树,再进行后面的左子树右子树操作,层序遍历也可以实现需要处理的位置

对称二叉树:我们需要比较里侧和外侧是否相等,只可以后序遍历,其实一个树是左右中,另一个是右左中,递归写法要注意考虑终止条件设定,迭代法使用栈和队列都可以实现,注意要传左右子树

二叉树的最大深度前序求得深度,后序求得高度,后序求的根节点高度来求的二叉树最大深度。深度是二叉树根节点到所求节点的距离,高度则是叶片节点到根节点,注意使用前序需要使用回溯,层序也可以实现每遍历一层深度+1,注n叉树就是子树多了而已,其实操作一样

二叉树的最小深度前序求得深度,后序求得高度,最小深度是从根节点到最近叶子节点的最短路径上的节点数量。,注意是叶子节点。左右孩子都为空的节点才是叶子节点,注意节点为空情况的讨论,左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。层序遍历只有当左右孩子都为空的时候,才说明遍历到最低点了。如果其中一个孩子不为空则不是最低点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/292211.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用贪心算法编程求解任务安排问题

题目&#xff1a;用贪心算法编程求解以下任务安排问题 一个单位时间任务是恰好需要一个单位时间完成的任务。给定一个单位时间任务的有限集S。关于S的一个时间表用于描述S中单位时间任务的执行次序。时间表中第1个任务从时间0 开始执行直至时间1 结束&#xff0c;第2 个任务从时…

MyBatisPlus学习一:快速入门

前言 前面快速学习了Mybatis&#xff0c;现在开始快速学习MyBatisPlus 学习教程&#xff1a; 黑马mybatis教程全套视频教程&#xff0c;2天Mybatis框架从入门到精通 黑马程序员最新MybatisPlus全套视频教程&#xff0c;4小时快速精通mybatis-plus框架 简介 MyBatisPlus 是…

解析《个人信息保护法》实施以来主要的变化

文章目录 前言一、二十一部配套的立法二、数据入表三、跨境规则转向四、未成年个人信息保护五、数据交易六、监管创新七、执法全覆盖八、地方聚焦场景执法九、个人信息保护诉讼十、个人信息保护公益诉讼十一、包容审慎十二、双清单上线十三、外部独立监督机构十四、个性化推荐便…

高清网络视频监控平台的应用-城市大交通系统视联网

目 录 一、应用需求 二、系统架构设计 三、功能介绍 1.实时视频监控 2.云台控制 3.语音功能 4. 录像管理与回放 5.告警联动 6.多种显示终端呈现 &#xff08;1&#xff09;CS客户端 &#xff08;2&#xff09;web客户端 &#xff08;3&#xf…

Proxy 与 defineProperty 的理解、区别、优势、劣势

一、Object.defineProperty() 文档&#xff1a;Object.defineProperty() - JavaScript | MDN 作用&#xff1a;对一个对象进行操作的方法。可以为一个对象增加一个属性&#xff0c;同时也可以对一个属性进行修改和删除。 它是在 ES5 中引入的&#xff0c;使用了 getter 和 s…

node加速镜像源 管理工具nrm安装使用

我们在开发node.js的时候,经常会遇到某些包无法下载, 或者下载太慢, 还有需要加载我们自己是有源中的包的问题, 今天推荐给大家的这款 nrm 镜像源管理工具就是解决这类问题的. 安装 方法也很简单, 执行 npm install nrm -g 就可以安装 # 安装nrm npm install nrm -g# 添加…

B端产品经理学习-对用户进行需求挖掘

目录&#xff1a; 用户需求挖掘的方法 举例&#xff1a;汽车销售系统的用户访谈-前期准备 用户调研提纲 预约用户做访谈 用户访谈注意点 我们对于干系人做完调研之后需要对用户进行调研&#xff1b;在C端产品常见的用户调研方式外&#xff0c;对B端产品仍然适用的 用户需…

yarn : 无法将“yarn”项不能识别为 cmdlet、function( is not recognized报错)

文章目录 在vscode终端使用yarn install命令下面报错在vscode控制台输入(全局安装yarn) :npm install -g yarn在执行&#xff0c;报错如下&#xff1a; 报错原因:报错进行修改如下:查看命令窗口执行的安全策略设置命令窗口执行的远程策略查看安全策略&#xff0c;已修改成功可以…

windows x86 calling convention

stdcall 全部压入栈里面 第一个参数最后一个入栈&#xff08;在栈顶&#xff09; fastcall ecx edx前两个 后面的压栈&#xff0c;顺序和stdcall一样

类加载机制之双亲委派模型、作用、源码、SPI打破双亲委派模型

双亲委派模型 双亲委派工作机制双亲委派的作用双亲委派的实现源码SPI打破双亲委派 应用程序是由三种类加载器相互配合&#xff0c;从而实现类加载&#xff0c;除此之外还可以加入自己定义的类的加载器。 类加载器之间的层次关系&#xff0c;称为双亲委派模型&#xff08;Parent…

印象笔记04: 如何将印象笔记超级会员价值最大化利用?

印象笔记04&#xff1a; 如何将印象笔记超级会员价值最大化利用&#xff1f; 为什么有这个问题 我不知道有没有人一开始接触印象笔记觉得非常好。奈何只能两个设备同步&#xff0c;局限太多。而会员活动比较优惠——就开了会员。而且我开了十年……。只能开发一下看看怎么最大…

C#用StringBuilder高效处理字符串

目录 一、背景 二、使用StringBuilder便捷、高效地操作字符串 三、实例 1.源码 2.生成效果 四、实例中知识点 1.StringBuilder类 一、背景 符串是不可改变的对象&#xff0c;字符串在创建以后&#xff0c;就不会被改变&#xff0c;当使用字符串对象的Replace、split或Re…

Flappy Bird QDN PyTorch博客 - 代码解读

Flappy Bird QDN PyTorch博客 - 代码解读 介绍环境配置项目目录结构QDN算法重要函数解读preprocess(observation)DeepNetWork(nn.Module)BirdDQN类主程序部分 介绍 在本博客中&#xff0c;我们将介绍如何使用QDN&#xff08;Quantile Dueling Network&#xff09;算法&#xf…

RK3399平台入门到精通系列讲解(实验篇)信号驱动 IO 实验

🚀返回总目录 文章目录 一、什么是信号驱动IO1.1、信号驱动IO1.2、fcntl 函数介绍二、信号驱动 IO 实验源码2.1、Makefile2.2、驱动部分代码2.3、测试应用代码一、什么是信号驱动IO 1.1、信号驱动IO 信号驱动 IO 不需要应用程序查询设备的状态,一旦设备准备就绪,会触发 SI…

ASP.NETCore WebAPI 入门 杨中科

ASP.NETCore WebAPI入门1 回顾 mvc开发模式 前端代码和后端代码是混在一个项目之中 WEB API 1、什么是结构化的Http接口。Json。 2、Web API项目的搭建。 3、Web API项目没有Views文件夹。 4、运行项目&#xff0c;解读代码结构。 5、【启用OpenAPI支持】→>swagger,在界…

视频文字想要提取应该使用哪些软件呢

随着短视频的兴起&#xff0c;快手成为了很多人喜爱的平台。有时候&#xff0c;我们看到一些有趣的视频&#xff0c;想要提取其中的文字内容&#xff0c;却不知道该如何操作。今天&#xff0c;我们就来介绍一种使用水印云快手提取视频文字的方法。 首先&#xff0c;我们需要下…

Java Review - Spring BeanUtils 踩坑记

文章目录 概述Spring BeanUtils基本使用Code忽略了属性类型导致拷贝失败同一字段在不同的类中定义的类型不一致同一个字段分别使用包装类和基本类型且没有传递实际值布尔类型的属性分别使用了基本类型和包装类型且属性名使用is开头 null值覆盖导致数据异常内部类数据无法成功拷…

根本记不住MySQL进阶查询语句

1 MySQL进阶查询 1.1 MySQL进阶查询的语句 全文以数据库location和Store_Info为实例 ---- SELECT ----显示表格中一个或数个字段的所有数据记录 语法&#xff1a;SELECT "字段" FROM "表名"; select 列名 from 表名 ; ---- DISTINCT ----不显示重复的数…

使用KVM命令集管理虚拟机

14.2.1案例分析 案例环境使用一台物理机器&#xff0c;一台服务器安装CentOS7.3的64位系统&#xff08;即node01&#xff09;&#xff0c;rhel7.1是在宿主机node01中安装的虚拟机。 14.2.2案例实施 1.安装Linux虚拟机 安装过程同上一案例&#xff0c;使用Xshell 远程控制node0…

视频号上怎么开店带货?门槛和注意事项,如下所示

我是王路飞。 视频号上现在也可以开店带货了&#xff08;严格来说从22年就可以了&#xff09;。 我们团队是在22年9月份开始入局视频号电商这个赛道的&#xff0c;在此之前是专注于抖店&#xff0c;目前两个项目都在做。 今天不聊抖店&#xff0c;主要说下视频号上开店带货的…