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

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

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

快速排序的逻辑是,若要对 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存储,键为序列化的子树,值为该子树出现的次数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值