当前位置: 首页 > ops >正文

分治算法之归并排序

在这里插入图片描述

每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry”

绪论​:
本章将学习分治算法,将通过练习促学习的方式来学习,主要通过先从归并算法开始带你打好基础,后续三道困难题都将在这个排序的基础上进行扩展,利用归并的思想来解决,在排序的过程中快速查找在自己后面的比自己小的值(降序排序),以及在自己前面比自己大的值(升序排序),具体细节让我们见文章吧,。
————————
早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。


分治(归并排序)

具体训练:

1. 归并排序

题目:

在这里插入图片描述
归并排序的核心思想:

  1. 根据中间的mid分成两部分:
  2. 先排左边:内部有是一个归并,当数组只有一个值时返回
  3. 再拍右边:内部同样是一个归并,当数组只有一个值时返回
  4. 将左右两个数组合并成一个有序数组返回
class Solution {
public:vector<int> tmp;vector<int> sortArray(vector<int>& nums) {msort(nums,0,nums.size()-1);return nums;}//Merge Sortvoid msort(vector<int>& nums,int left,int right){if(left >= right) return ;int mid = left + (right - left) / 2;//mid 1 2 3 4 mid = 1// 2 3 mid msort(nums,left,mid);msort(nums,mid+1,right);//left ~ mid - 1//mid ~ righttmp.clear();int cur1 = left,cur2 = mid + 1;while(cur1 <= mid  && cur2 <= right){if(nums[cur1] < nums[cur2]) tmp.push_back(nums[cur1++]);else tmp.push_back(nums[cur2++]);}//处理剩下的while(cur1 <=  mid){tmp.push_back(nums[cur1++]);//处理剩下的}while(cur2 <= right){tmp.push_back(nums[cur2++]);}for(int i = left;i <= right;i++)nums[i] = tmp[i - left];}
};

其中本质就是后序遍历的方法:先遍历左,左边结束后才到右,最后到结点:
在这里插入图片描述

快排是每一层选择一个基准元素key,然后进行分组后重复,直到数组分成一个元素时分组结束
在这里插入图片描述
而快排就相当于一种前序遍历,处理完结点后分成两组后再分别处理:
本质区别就是处理数组时机不同

2. 交易逆序对的总数

题目:

在这里插入图片描述

分析题目

对于暴力解法来说:比较简单两层暴力枚举即可找到最多的逆序对(但一定是会超时的)
优化方法:
在这里插入图片描述
上图描述:

  1. 首先将一整个区间划分成两部分 a、b
  2. 然后在新区域a、b中找逆序对,得到a、b个
  3. 然后再将 a、b区域联合起来看,选择一个a区域中的逆序对,然后再选择一个b区域的,最终组合成新的逆序对,个数为c
  4. 那么最终逆序对个数为 a + b + c

顺序是:先挑a区域、再挑b区域、最后查询 a 和 b 组合(左半部分、右半部分、一左一右)
那么再次优化,先找左半部分,找完后进行排序、同理找完右半部分,进行排序,最后一左一右(因为排序后找一左一右就有单调性了就能快速的确定个数!)
最终:本质就很归并排序是一样的了!!(如下图)
在这里插入图片描述
其中对合并排序和其中需要的其他操作来找结果的详细解释(如下图):
在这里插入图片描述

当到达归并排序的某一层时的操作:

  1. 首要目的是为了合并,但同时也要记录逆序对的个数!
  2. 回顾个数的计算:a(左) + b(右) + c(左右),其中a、b在上一层已经计算了,所以本层只用计算 c 即可
  3. 记录个数(c 一左一右):那么也就是要找 cur1 后面能接的 cur2(记住)
  4. 回到公式当 num[cur1] <= num[cur2] 时就正常进行归并排序,cur1++
  5. 而当 num[cur1] > num[cur2] 时,这时cur1即往后的值都是大于cur2的(因为a区间是始终升序的),所以个数就能直接增加 ret += mid - cur1 + 1个,然后进行正常归并排序步骤cur2++

题目核心逻辑

class Solution {
public:int reversePairs(vector<int>& record) {return mergesort(record,0,record.size()-1);}//归并int mergesort(vector<int>& record,int left,int right){if(left >= right) return 0;int mid = left + (right - left) / 2;int a = mergesort(record,left,mid);int b = mergesort(record,mid+1,right);vector<int> temp;// cint c= 0;//进行归并,此处是升序,取出小的放入容器中int cur1 = left,cur2 = mid+1;while(cur1 <= mid & cur2 <= right){//策略1:找前面有多少大的,针对cur2来看if(record[cur1] > record[cur2]){c += mid - cur1 + 1;temp.push_back(record[cur2]);cur2++;}else{//cur1 <= cur2temp.push_back(record[cur1]);cur1++;}}while(cur1 <= mid) temp.push_back(record[cur1++]);while(cur2 <= right) temp.push_back(record[cur2++]);for(int j = left; j <= right; j++)record[j] = temp[j - left];return a + b + c;}};

3. 计算右侧小于当前元素的个数

题目:

在这里插入图片描述

分析题目

分析题目和例1:

  • 找当前这个数的右边有多少比自己小的
    在这里插入图片描述
    所以就和上题比较像,可以使用
    上一题中的第二个策略:找当前元素后边有多少个比我小前提降序

  • 在降序的前提下(这里的降序,本质就是在归并排序中执行的排序操作)

  • 排序后就是内部的一些问题,同样的也是 左区间 + 右区间 + 左右区间

  • 其中具体已经在上题中说明了,本题主要还是讲解左右区间:

  • 通过比较cur1、cur2找到比自己小的元素,具体公式如下图

  • 本题需要返回的是一个数组nums,其中它对应的是,它上方数值右边比他小的个数

  • 当cur1 > cur2时,因为是降序cur2右边都将比他小,所以就直接加上right - cur2 - 1
    在这里插入图片描述

如何处理当排序后其索引改变导致找不到原数据的问题

  • 那么其中就会有一个问题,当排完序后如何仍然找到对应的原始的下标呢(因为排完序后位置就发生了改变,低下记录值的数组nums就将不再对应上,所以就需要进行一定的处理)
  • 一般来说可以使用hash的kv进行绑定值和下标,但本题可能会出现重复的值所以就不适合使用
  • 那么可以使用一个index新数组进行绑定管理索引,其中在操作nums的同时index也是同样的改变
  • 这样index数组中的索引就始终对应了
    在这里插入图片描述

题目核心逻辑

本题重点:

  • 通过新增一个start_index索引数组来记录当前索引的位置
  • 并且在更改位置的过程中记录当前索引的位置,然后实时的也进行修改
  • 其中也多借助了一个index辅助数组进行存储移动前的索引的位置
class Solution {
public:vector<int> countSmaller(vector<int>& nums) {int n = nums.size();//归并排序vector<int> res(n);vector<int> start_index(n);for(int i = 0;i < nums.size();i++)start_index[i] = i;mergeSort(nums,res,start_index,0,n - 1);return res;}int temp[100010] = {};int index[100010] = {};void mergeSort(vector<int>& nums,vector<int>& res,vector<int>& start_index,int left,int right){if(left >= right) return ;//当超过范围就返回int mid = (right + left) / 2;//取中进行划分mergeSort(nums,res,start_index,left,mid);mergeSort(nums,res,start_index,mid+1,right);//a + b,合并的同时进行计算int p1 = left,p2 = mid + 1,ti = 0;while( p1 <= mid && p2 <= right){//降序,等于 在归并排序中本质放到哪里都行,但在本题需要找的是 p1 > p2 的才个数,所以将 等于 放到 < 处if(nums[p1] <= nums[p2]){index[ti] = start_index[p2];//存储他的下标temp[ti++] = nums[p2++]; }else{//记录长度//start_index是他的原始索引位置res[start_index[p1]] += right - p2 + 1;index[ti] = start_index[p1];//存储他的下标temp[ti++] = nums[p1++];}}while(p1 <= mid) {res[start_index[p1]] += right - p2 + 1;index[ti] = start_index[p1];//存储他的下标temp[ti++] = nums[p1++];}while(p2 <= right) {index[ti] = start_index[p2];//存储他的下标temp[ti++] = nums[p2++]; }//将temp放入数组for(int i = left;i <= right ;i++){start_index[i] = index[i - left];//让原数位置也跟着移动nums[i] = temp[i - left];//移动值的同时移动index//num[i]是新值,i是新}}};

在这里插入图片描述

4. 翻转对

题目:

在这里插入图片描述

分析题目

  • 分析题目:找出所有值是后面值的两倍的的个数
  • 如下图:3 > 1*2 共有两次所以也就是2
    在这里插入图片描述
    那么类似的还是找比自己小的元素,只不过此时是小两倍的元素,就会导致运算情况和归并并不一样了
    此时就需要将算左 右区间的情况单独出来看,用一个双指针的方式(这里就不过诉了看下图)来提前处理排好序的值,找到左 右区间合起来看时有多少符合翻转对的值,具体为什么不能放到归并中操作下述代码中已经注释
  • 一个基础的单调性双指针算法(有问题可以评论)
    在这里插入图片描述

题目核心逻辑

  • 具体细节写在注释
class Solution {
public:int reversePairs(vector<int>& nums) {return mergeSort(nums,0,nums.size()-1);}int tmpNums[50010];int mergeSort(vector<int>& nums, int left, int right) {if (left >= right)return 0;int mid = (left + right) / 2;int ret = 0;// [left, mid] [mid + 1, right]// 2. 处理左右两个区间ret += mergeSort(nums, left, mid);ret += mergeSort(nums, mid + 1, right);// 3. 处理⼀左⼀右的情况int cur1 = left, cur2 = mid + 1, i = 0;//双指针提前处理,计算左右区间中的值while (cur1 <= mid && cur2 <= right) // 降序排序{//主要不同就是在这里,不能再归并中同时处理就是因为,可能会跳过处理某些情况//若:当cur1 > cur2 时会不断的走cur1的而不会动 cur2,这样就会导致 cur1 > 2*cur2 其中的cur2不变导致 ret 无法遍历所有情况if (nums[cur1] >(long long) 2*nums[cur2]) {cur1++;ret += right - cur2 + 1;} else {cur2++;}}cur1 = left, cur2 = mid + 1;//恢复值//不放在一起的原因是,他们运算的过程不一致,主要体现在 nums[cur1] > 2*num[cur2] 时才需要记录答案,这里有许多细节//并不好叙述,主要就是理解他们 运算是不一样的 会导致某些问题的方式从而让 cur2 和 cur1 的比较并不正常(具体如上描述)while (cur1 <= mid && cur2 <= right) // 降序排序{if (nums[cur1] <= nums[cur2]) {tmpNums[i++] = nums[cur2++];} else {tmpNums[i++] = nums[cur1++];}}// 4. 处理剩余的排序⼯作while (cur1 <= mid) {tmpNums[i++] = nums[cur1++];}while (cur2 <= right) {tmpNums[i++] = nums[cur2++];}for (int j = left; j <= right; j++) {nums[j] = tmpNums[j - left];}return ret;}
};
http://www.xdnf.cn/news/14481.html

相关文章:

  • Day04_C语言基础数据结构重点复习笔记20250618
  • 反转链表二--LeetCode
  • Neo4j 入门到精通(Cypher语言详解)
  • 前端部署更新后,如何优雅地通知用户刷新页面?
  • OpenCV——图像形态学
  • Arrays.asList() 的不可变陷阱:问题、原理与解决方案
  • 秋招是开发算法一起准备,还是只准备一个
  • 技能系统详解(1)——技能
  • mysql 学习
  • 45-Oracle 索引的新建与重建
  • 6-16阿里前端面试记录
  • RAG 架构地基工程-Retrieval 模块的系统设计分享
  • 学习STC51单片机41(芯片为STC89C52RCRC)智能小车8(测速显示到OLED显示屏)
  • git最常用命令
  • RISC-V向量扩展与GPU协处理:开源加速器设计新范式——对比NVDLA与香山架构的指令集融合方案
  • 汽车 CDC威胁分析与风险评估
  • HTTP 请求中的 `Content-Type` 类型详解及前后端示例(Vue + Spring Boot)
  • 腾讯云国际站缩容:策略、考量与实践
  • Vue-7-前端框架Vue之应用基础从Vue2语法到Vue3语法的演变
  • C/C++中的位段(Bit-field)是什么?
  • 单片机 - STM32读取GPIO某一位时为什么不能直接与1判断为高电平?
  • 【开源工具】Windows屏幕控制大师:息屏+亮度调节+快捷键一体化解决方案
  • Day03_数据结构(顺序结构单向链表单向循环链表双向链表双向循环链表)
  • 【一天一个知识点】RAG(Retrieval-Augmented Generation,检索增强生成)构建的第一步
  • ARIMA 模型
  • Linux运维新人自用笔记(部署 ​​LAMP:Linux + Apache + MySQL + PHP、部署discuz论坛)
  • 内存泄漏到底是个什么东西?如何避免内存泄漏
  • 楞伽经怎么读
  • 23种设计模式图解
  • ragflow中的pyicu安装与测试