二叉树算法-----从零开始的算法

一.前言:

二叉树的解题思维可以分为

1.遍历: 通过遍历一遍二叉树得到答案(二叉树遍历----从零开始的数据结构-CSDN博客

  1. 分解思想: 定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案。

无论哪种想法都要考虑到:一个二叉树节点它需要是 "前序" ,"中序" ,"后序"做。

二.深度理解二叉树的前/中/后序

回忆一下二叉树的前中后序框架:

cpp 复制代码
#include<iostream>
using namespace std;

class TreeNode{
public:
   int val;
   TreeNode* left;
   TreeNode* right;

   TreeNode(int val) : left(nullptr),right(nullptr) , val(val){}
};//用结构体也是一样的


void traverse(TreeNode* root){
    if (root == nullptr){
        return;
    }
    
    //前序
    traverse(root->left);
    //中序
    traverse(root->right);
    //后序
    
}//就是一个游走的指针

再来回顾一下链表和数组的遍历,就会发现它们和二叉树遍历几乎没有什么不同

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;

void traverse(vector<int> arr){
    for (int x : arr){

    }
}//迭代遍历数组

void traverse(vector<int>& arr, int i){
    if (i == arr.size()) return;
    
    //前序
    traverse(arr,i+1);
    //后序
}//递归遍历数组





//链表
class ListNode{
   public:
     int val;
     ListNode* next;

     ListNode(int x) : next(nullptr) , val(x){}
};


void traverse(ListNode* head){
      for (ListNode* temp = head; temp != nullptr ;temp = temp->next){
         
      }
}//迭代


void traverse(ListNode* head){
    if (head == nullptr){
        return;
    }
    //前序
    traverse(head->next);
    //后序
}

• 我们会发现无论是数组还是链表,只要是递归的形式中 就一定会存在前序后序

二叉树遍历的时候多了一个中序遍历罢了。

前序位置: 在进入一个节点之前进行

后序位置: 在离开一个节点之前进行

中序: 二叉树遍历完左节点,即将遍历右节点的时候进行

++我们会发现上一期二叉树遍历时候提到的前中后序中的根出现的位置,就是以上三种位置遍历进行的位置!++

三.两种解题思想:

例题:leetcode104 二叉树最大深度

给定一个二叉树 root ,返回其最大深度。

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

示例 1:

复制代码
输入:root = [3,9,20,null,null,15,7]
输出:3

示例 2:

复制代码
输入:root = [1,null,2]
输出:2

提示:

  • 树中节点的数量在 [0, 104] 区间内。
  • -100 <= Node.val <= 100

• 遍历思想:

cpp 复制代码
#include<iostream>
#include<algorithm>
using namespace std;


class Solution {
    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) {}
  };

    public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr) return 0;
        int depth = INT_MIN;
        int current = 1;
        traverse(root,depth,current);

        return depth;
    }

    private:
    void traverse(TreeNode* root,int& depth,int& current){
         if (root == nullptr) {
            int temp = current;
             depth = max(depth,temp - 1);
             return;
         }
         
         current++;
         traverse(root->left,depth,current);
         traverse(root->right,depth,current);
         current--;
    }
};

根节点深度为1,然后遍历寻找最大深度,通过current深度 和 depth最大深度比较

把depth深度设置为INI_MIN无穷小,然后利用max函数比较得出

在前序进入节点前+1 ,在离开节点的时候减去1

• 分解思想:

cpp 复制代码
#include<iostream>
using namespace std;


class Solution {
  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) {}
  };

    public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr) return 0;
        
        int left = maxDepth(root->left);
        int right = maxDepth(root->right);

        return 1 + max(left,right);
    }
};

分解思想要从根开始看---就是最初的问题

然后分解成两个子问题,比如说一个树的最大深度,其实就是比较两个子树的最大深度,

这样遍历左子树,右子树层层遍历下去

你会发现分解问题中也运用了遍历,分解是实现遍历的一种方法。

掌握分解,不应该从最小那一个部分,从小到大看,++而是从大到小拆开。++

例题:144二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:

**输入:**root = [1,null,2,3]

输出:[1,2,3]

解释:

示例 2:

**输入:**root = [1,2,3,4,5,null,8,null,null,6,7,9]

输出:[1,2,4,5,6,7,3,8,9]

解释:

示例 3:

**输入:**root = []

输出:[]

示例 4:

**输入:**root = [1]

输出:[1]

提示:

  • 树中节点数目在范围 [0, 100]
  • -100 <= Node.val <= 100

**进阶:**递归算法很简单,你可以通过迭代算法完成吗?

这题用遍历很简单

这里具体说说如何用"分解问题" 的思路 来解决这道题

前缀遍历的顺序是 根 ---- 左子树---右子树

那我们分解问题,也分成加入根节点 插入 左子树 和 右子树

如下:

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;



class Solution {
   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) {}
   };

    public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> arr;
        if (root == nullptr) return {};

        arr.push_back(root->val); // 插入根节点

        vector<int> left = preorderTraversal(root->left);
        arr.insert(arr.end(),left.begin(),left.end());
        vector<int> right = preorderTraversal(root->right);
        arr.insert(arr.end(),right.begin(),right.end());

        return arr;
    }
};
小总结:当你发现题目中存在类似 "子树"/ "子问题"的时候,不妨尝试一下分治/分解的思想,进行递归

三.前/中/后序的性质:

• 前序:只能获取从函数参数那里获取的父节点传来的数据

• 中序: 不仅获取父节点传来的数据,还获取函数参数那里传来的数据

• 后序: 获取左右子树传来的数据和父节点传来的数据

后序写法: 通常运用的都是 "分解"的思想,分解成当前节点和左右节点的问题

例题:543.二叉树的直径

给你一棵二叉树的根节点,返回该树的 直径

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root

两节点之间路径的 长度 由它们之间边数表示。

示例 1:

复制代码
输入:root = [1,2,3,4,5]
输出:3
解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。

示例 2:

复制代码
输入:root = [1,2]
输出:1

提示:

  • 树中节点数目在范围 [1, 104]
  • -100 <= Node.val <= 100

求二叉树的最大直径本质上就是 求某一个节点,++它的左右子树的最大深度之和最大++

遍历解法:

cpp 复制代码
#include<iostream>
#include<algorithm>
using namespace std;

class Solution {
  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) {}
  };

    public:
    int diameterOfBinaryTree(TreeNode* root) {
        if (root == nullptr) return 0;
        int length = INT_MIN;
        traverse(root,length);

        return length;
    }

    private:
    void traverse(TreeNode* root, int& length){
        if (root == nullptr) return;
         
        int leftmax = maxdepth(root->left);
        int rightmax = maxdepth(root->right);
        int temp = leftmax + rightmax;
        
        length = max (temp,length);

        traverse(root->left,length);
        traverse(root->right,length);
    }

    int maxdepth(TreeNode* root){
        if (root == nullptr) return 0;

        int leftmax = maxdepth(root->left);
        int rightmax = maxdepth(root->right);

        return 1 + max(leftmax,rightmax);
    }
};

遍历的解法本质上就是原函数+遍历二叉树的每一个节点。

在这个解法中我们是 运用的"前序" 的写法,这表示我们只能知道函数传来的参数和父节点的信息,因此我们需要再利用 "一个寻找最大深度的" 递归函数,来得知下一个节点的左/右子树的最大深度

• 分解写法:

cpp 复制代码
#include<iostream>
#include<algorithm>
using namespace std;


class Solution {
  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) {}
  };

    public:
    int diameterOfBinaryTree(TreeNode* root) {
        if (root == nullptr) return 0;
        int sum = INT_MIN;
        maxdepth(root,sum);
        return sum;
    }

    private:
    int maxdepth(TreeNode* root, int& sum){
       if (root == nullptr) return 0;

       int leftmax = maxdepth(root->left,sum);
       int rightmax = maxdepth(root->right,sum);
       int temp = leftmax + rightmax;
       sum = max(sum,temp); 
       
       return 1 + max(leftmax,rightmax);
    }
};

我们在这个分解写法中运用了 "后序"遍历,那么我们就知道左右子树的所有信息,因此我们就可以直接比较该节点的下一个节点的左右子树的最大深度,

然后最后再把该节点的通过其左右节点的计算出的最大子树深度相加,与之前的最大值进行比较,更改最大值

最后剩下的sum就是我们所求的答案

结语:

不知道大家有没有一个发现呢?

++当运用到 "分解"思想 的时候,递归函数往往就需要一个返回值++

++而我们在遍历得到答案的时候,递归函数却不需要返回值++

喜欢这篇文章不妨点一个免费的赞和收藏

相关推荐
睡觉就不困鸭2 小时前
第十七天 翻转字符串里的单词
数据结构·算法·哈希算法·散列表
ulias2122 小时前
leetcode热题 - 4
算法·leetcode·职场和发展
学术阿凡提2 小时前
Spring Boot 优雅实现异步调用:从入门到自定义线程池与异常处理
java·数据库·算法
MicroTech20252 小时前
微算法科技(NASDAQ :MLGO)量子化边缘检测技术:重塑图像处理的新范式
图像处理·科技·算法
WolfGang0073212 小时前
代码随想录算法训练营 Day47 | 图论 part05
算法·图论
猿长大人2 小时前
算法 | 轮廓提取随笔 —— 关于像素、阈值和直觉的碎碎念
图像处理·算法
啦啦啦_99993 小时前
1. 线性回归之 向量&矩阵
算法·矩阵·线性回归
DolphinDB智臾科技3 小时前
DolphinDB 走进东南大学 | 新型电力系统高频数据处理与算法落地实战
算法
Zzzzmo_3 小时前
前缀和算法
算法·前缀和