数据结构&算法——二叉树

Posted vector6_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构&算法——二叉树相关的知识,希望对你有一定的参考价值。

数据结构&算法——二叉树

很多经典算法都是二叉树的变形。比如说快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历

快速排序的逻辑是,若要对 nums[lo..hi] 进行排序,我们先找一个分界点 p,通过交换元素使得 nums[lo..p-1] 都小于等于 nums[p],且 nums[p+1..hi] 都大于 nums[p],然后递归地去 nums[lo..p-1]nums[p+1..hi] 中寻找新的分界点,最后整个数组就被排序了。

快速排序的算法框架:

void sort(int[] nums, int lo, int hi) 
    /****** 前序遍历位置 ******/
    // 通过交换元素构建分界点 p
    int p = partition(nums, lo, hi);
    /************************/

    sort(nums, lo, p - 1);
    sort(nums, p + 1, hi);

对于归并排序,若要对 nums[lo..hi] 进行排序,我们先对 nums[lo..mid] 排序,再对 nums[mid+1..hi] 排序,最后把这两个有序的子数组合并,整个数组就排好序了。归并排序的代码框架如下:

void sort(int[] nums, int lo, int hi) 
    int mid = (lo + hi) / 2;
    sort(nums, lo, mid);
    sort(nums, mid + 1, hi);

    /****** 后序遍历位置 ******/
    // 合并两个排好序的子数组
    merge(nums, lo, mid, hi);
    /************************/

二叉树问题的重点是搞清楚当前 root 节点“该做什么”以及“什么时候做”,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情。

其中“该做什么”指在当前节点需要的处理行为,“什么时候做”指考虑这段代码到底应该写在前序、中序还是后序遍历的代码位置上。

翻转二叉树

// 将整棵树的节点翻转
TreeNode invertTree(TreeNode root) 
    // base case
    if (root == null) 
        return null;
    

    /**** 前序遍历位置 ****/
    // root 节点需要交换它的左右子节点
    TreeNode tmp = root.left;
    root.left = root.right;
    root.right = tmp;

    // 让左右子节点继续翻转它们的子节点
    invertTree(root.left);
    invertTree(root.right);
    
    return root;

填充二叉树节点的右侧指针

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

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

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

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

class Solution 
public:
    void connectHelper(Node* node1, Node* node2)
    
        if(node1==nullptr)
            return;
        /**** 前序遍历位置 ****/
    	// 将传入的两个节点连接
        node1->next=node2;
        // 连接相同父节点的两个子节点
        connectHelper(node1->left, node1->right);
        connectHelper(node2->left, node2->right);
        // 连接跨越父节点的两个子节点
        connectHelper(node1->right,node2->left);
    

    Node* connect(Node* root) 
        if(root==nullptr)
            return nullptr;
        connectHelper(root->left, root->right);
        return root;
    
;

如果我们在当前根节点只是将其左右子节点连接,可能出现的问题是,对于不是同一个父节点的两个相邻节点(跨越父节点的两个子节点)无法被连接。如果只依赖一个节点的话,肯定是没办法连接跨父节点的两个相邻节点的那么,我们的做法就是增加函数参数,一个节点做不到,我们就给他安排两个节点,「将每一层二叉树节点连接起来」可以细化成「将每两个相邻节点都连接起来」。

将二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:

展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。

class Solution 
public:
    void flatten(TreeNode* root) 
        if(root == nullptr)
        
            return;
        
        flatten(root->left);	//先将左右子树都展开
        flatten(root->right);
        TreeNode* temp = root->right;//再将左子树放在右指针位置,左子树的最后一个节点指向右子树
        root->right = root->left;
        root->left = nullptr;
        while(root->right!=nullptr)
        
            root = root->right;
        
        root->right = temp;
    
;

最大二叉树

给定一个不含重复元素的整数数组 nums 。一个以此数组直接递归构建的 最大二叉树 定义如下:

二叉树的根是数组 nums 中的最大元素。
左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。
右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。
返回有给定数组 nums 构建的 最大二叉树 。

/**
 * 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 getMax(vector<int> nums, int low, int high)
    
        if(low==high)
            return low;
        int maxIndex = low, maxNum = nums[low];
        
        for(int i = low+1; i <= high; ++i)
        
            if(maxNum<nums[i])
            
                maxNum = nums[i];
                maxIndex = i;
            
        
        return maxIndex;
    

    TreeNode* buildNode(vector<int> nums, int low, int high)
    
        if(nums.size() == 0 || low > high)
            return nullptr;
        if(low==high)
        
            TreeNode* node = new TreeNode(nums[low]);
            return node;
        
        int index = getMax(nums, low, high);
        TreeNode* root = new TreeNode(nums[index]);
        root->left = buildNode(nums, low, index-1);
        root->right = buildNode(nums, index+1, high);
        return root;
    

    TreeNode* constructMaximumBinaryTree(vector<int>& nums) 

        if(nums.size()==0)
            return nullptr;
        TreeNode* root = buildNode(nums, 0, nums.size()-1);
        return root;
    
    
;

对于每个根节点,只需要找到当前 nums 中的最大值和对应的索引,然后递归调用左右数组构造左右子树即可。对于这种通过索引去递归的,最好使用辅助函数显式地传入(维护)索引,更清晰不易出错。

寻找重复的子树

给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。

两棵树重复是指它们具有相同的结构以及相同的结点值。

/**
 * 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:
    unordered_map<std::string, int> subTreeMap; //如果有2个以上重复的子树,结果中只能返回一次,所以使用unordered_map存储,键为序列化的子树,值												   //为该子树出现的次数。
    vector<TreeNode*> result;
    vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) 
        traverse(root);
        return result;
    

    std::string traverse(TreeNode* root)
    
        if(root == nullptr)
        
            return "#";
        
        std::string left = traverse(root->left);
        std::string right = traverse(root->right);
        string sTree = left + "," + right + "," + to_string(root->val);//序列化当前子树
        if(subTreeMap.find(sTree) != subTreeMap.end())	//判断是否有重复子树
        
            if(subTreeMap[sTree]==1)					//若重复子树添加过,则不需要再添加
            
                result.push_back(root);
                subTreeMap[sTree]++;
            
        
        else
        
            subTreeMap.insert(make_pair(sTree, 1));
        
        
        return sTree;
    
;

要判断以当前节点为根的子树是否有与之相同的子树,首先要清楚自己所在子树的具体内容(后序遍历求得),其次还要知道其他子树的情况(使用map存储其他子树的),当然这里需要把子树序列化来存储,常用的形式:left + “,” + right + “,” + to_string(root->val) (后序)。要注意的是如果有2个以上重复的子树,结果中只能返回一次,所以使用unordered_map存储,键为序列化的子树,值为该子树出现的次数。

以上是关于数据结构&算法——二叉树的主要内容,如果未能解决你的问题,请参考以下文章

Day584&585.平衡二叉树 -数据结构和算法Java

数据结构(C语言版) 树和二叉树 算法设计Demo3

数据结构与算法 —— 二叉树

算法之二叉树各种遍历

Java 数据结构 & 算法宁可累死自己, 也要卷死别人 11 二叉树

Java 数据结构 & 算法宁可累死自己, 也要卷死别人 11 二叉树