【置换环问题】逐层排序二叉树所需的最少操作数目

回顾题目

https://leetcode.cn/problems/minimum-number-of-operations-to-sort-a-binary-tree-by-level/description/

前言

由于本次想要主要分享置换环问题的思路,所以具体的题目内容就不列出来了,大家可以点击上方链接先浏览一下题目。

思路

题目要求将二叉树的每一层上的节点都按照升序排列,且只能通过每次交换同一层的两个节点来完成,最少的总交换次数作为题目最终的答案。简单来概括就是 层序遍历+排序

  • 层序遍历:本题需要对二叉树进行层序遍历,并存储每一层的节点值。因为层序遍历是从每一层的最左节点开始,自左向右遍历,所以选择用队列来保存每一层的节点序列,从根节点root开始对每一层进行遍历,代码如下:
vector<vector<int>> levelOrder(TreeNode* root) {
        if (root == nullptr)return {};
        TreeNode* curNode = nullptr;//当前节点
        vector<int> perLevelNodes;//存储每一层的节点值
        vector<vector<int>> res;
        int count = 0;
        queue<TreeNode*> nodeQueue;//节点序列
        nodeQueue.push(root);//根节点入列
        while (!nodeQueue.empty()) // 每轮循环,都访问一层节点
        {
            perLevelNodes.clear();//先清空当前节点
            count = nodeQueue.size(); // 当前层有count个节点
            for (int i = 0; i < count; ++i) 
            { // 循环弹出count次节点进行访问,也就是访问该层所有节点;同时将下一层所有节点压入栈
                curNode = nodeQueue.front();
                nodeQueue.pop();
                perLevelNodes.push_back(curNode->val);
                if (curNode->left != nullptr) // 左子节点非空压入栈
                    nodeQueue.push(curNode->left);
                if (curNode->right != nullptr) // 右子节点非空压入栈
                    nodeQueue.push(curNode->right);
            }
            res.push_back(perLevelNodes); // 将每层节点数组保存起来
        }
        return res; // 返回结果数组
    }
  • 排序:怎样用最少的交换次数使数组有序是本题的重点,不同于冒泡和快排,实现的方法是通过一个叫“置换环”的问题推导出来的。这里我先将置换环的结论给出,后面我会用图文进行介绍。最少交换次数 = 节点数 - 置换环的个数(环数)

有如下待排序的数组
在这里插入图片描述
排序之后
在这里插入图片描述
具体的交换过程如下图所示:
【置换环问题】逐层排序二叉树所需的最少操作数目_第1张图片
如图,最少的交换过程是:相同色块内的元素进行交换,每一组相同颜色的色块代表了一个“环”,“环”的特点是环的头尾相等,环中每“组”元素的“尾首相连”

“组”的含义即上图中,将排序前和排序后的数组纵排之后上下两两作为一组
“尾首相连”的含义是每“组”的最后一个元素等于环中某一“组”的第一个元素

例如:上图中的紫色色块中,最左边(74,28)与相邻的(28,47)尾首相连,(28,47)又与最右边的(47,86)尾首相连,(47,86)又与最后一个紫色色块(86,74)尾首相连,且最后的74与最开始的(74,28)中的74相等,形成一个环。满足这样性质的一组数就叫做一个置换环。
从图中不难看出,如果一个置换环中有两组元素(如绿色和灰色),则会进行一次交换,而有四组元素的置换环(紫色)需要进行三次交换,以上图为例:
最终的交换次数 = sum(每个环的大小-1) =(2-1)+(2-1)+(4-1)=(2+2+4)-(1+1+1)= 节点数 - 置换环的个数
具体的实现代码如下:

int getMinSwaps(vector<int> &nums){
        //排序
        vector<int> nums1(nums);
        sort(nums1.begin(),nums1.end());
        unordered_map<int,int> m;
        int n = nums.size();
        for (int i = 0; i < n; i++){
            m[nums1[i]] = i;//建立每个元素与其应放位置的映射关系
        }

        int loops = 0;//环数
        vector<bool> flag(n,false);
        //找出环数
        for (int i = 0; i < len; i++){
            if (!flag[i]){//已经访问过的位置不再访问
                int j = i;
                while (!flag[j]){
                    flag[j] = true;
                    j = m[nums[j]];//原序列中j位置的元素在有序序列中的位置
                }
                loops++;
            }
        }
        return len - loops;//返回最少交换次数=结点数-置换环的个数
    }

总代码+注释

/**
 * 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>> levelOrder(TreeNode* root) {
        if (root == nullptr)return {};
        TreeNode* curNode = nullptr;//当前节点
        vector<int> perLevelNodes;//存储每一层的节点
        vector<vector<int>> res;
        int count = 0;
        queue<TreeNode*> nodeQueue;
        nodeQueue.push(root);//根节点入列
        while (!nodeQueue.empty()) // 每轮循环,都访问一层节点
        {
            perLevelNodes.clear();//先清空当前节点
            count = nodeQueue.size(); // 当前层有count个节点
            for (int i = 0; i < count; ++i) 
            { // 循环弹出count次节点进行访问,也就是访问该层所有节点;同时将下一层所有节点压入栈
                curNode = nodeQueue.front();
                nodeQueue.pop();
                perLevelNodes.push_back(curNode->val);
                if (curNode->left != nullptr) // 左子节点非空压入栈
                    nodeQueue.push(curNode->left);
                if (curNode->right != nullptr) // 右子节点非空压入栈
                    nodeQueue.push(curNode->right);
            }
            res.push_back(perLevelNodes); // 将每层节点数组保存起来
        }
        return res; // 返回结果数组
    }
    int getMinSwaps(vector<int> &nums){
        //排序
        vector<int> nums1(nums);
        sort(nums1.begin(),nums1.end());
        unordered_map<int,int> m;
        int n = nums.size();
        for (int i = 0; i < n; i++){
            m[nums1[i]] = i;//建立每个元素与其应放位置的映射关系
        }

        int loops = 0;//环数
        vector<bool> flag(n,false);
        //找出环数
        for (int i = 0; i < len; i++){
            if (!flag[i]){//已经访问过的位置不再访问
                int j = i;
                while (!flag[j]){
                    flag[j] = true;
                    j = m[nums[j]];//原序列中j位置的元素在有序序列中的位置
                }
                loops++;
            }
        }
        return len - loops;
    }
    int minimumOperations(TreeNode* root) {
        vector<vector<int>> nums=levelOrder(root);
        int ans=0;
        for(auto x:nums){
            ans+=getMinSwaps(x);
        }
        return ans;
    }
};

本文章旨在为大家提供置换环的问题的解法及思路,关于为何通过置换环的方法得出的交换次数就是最少交换次数的证明,推荐大家看一下这个博主的数理证明过程,非常详细。
最少交换次数证明

你可能感兴趣的:(算法,leetcode,数据结构,力扣,c++)