【优选算法篇】:分而治之--揭秘分治算法的魅力与实战应用

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:优选算法篇–CSDN博客

在这里插入图片描述

文章目录

  • 一.什么是分治算法
    • 1.分治算法的基本概念
    • 2.分治算法的三个步骤
  • 二.分治算法的应用实例
    • 1.快速排序(`Quick Sort`)
      • 1.颜色划分
      • 2.排序数组
      • 3.数组中的第K个最大元素
      • 4.最小的k个数
    • 2.归并排序(`Merge Sort`)
      • 1.排序数组
      • 2.交易逆序对的总数
      • 3.计算右侧小于当前元素的个数
      • 4.翻转对

一.什么是分治算法

1.分治算法的基本概念

分治算法(Divide-and-Conquer Algorithm)是一种重要的算法设计范式。它的核心思想是将一个复杂的,规模较大的问题分解成若干个规模比较小,相互独立且与原问题类型相干的子问题,然后逐个解决这些子问题,最后再将子问题的解合并成原问题的解。

2.分治算法的三个步骤

  • 分解(Divide
    • 这一步是将原问题分解成多个规模的更小的子问题。例如,对于一个数组排序问题,可以将数组不断地分成两半,直到子数组的规模足够小(例如,在归并排序中,只有一个元素时就认为是已经有序的)。
    • 分解的方式需要根据具体的问题而定。在计算矩阵乘法时,可以将大矩阵分解为多个小矩阵;在处理图像时,可以将大图像分解为多个小区域。
  • 解决(Conquer
    • 对于分解后的子问题,如果子问题的规模足够小,就可以直接求解。比如,当子数组只有一个元素时,他本是就是有序的,这就是直接求解的情况。
    • 如果子问题任然比较复杂,就递归的调用分治算法来继续分解和求解子问题。例如,在归并排序中,不断将数组分解后,对每个子数组继续使用归并排序,直到子数组规模为1。
  • 合并(Combine
    • 在子问题得到解决后,需要将子问题的解合并起来,已得到原始问题的解。例如在归并排序中,将两个有序的子数组合并成一个更大的有序数组。
    • 合并的操作也需要根据具体问题进行设计。在计算大整数乘法时,将子问题计算得到的部分乘积合并起来得到最终的结果;在处理图像分割问题时,将各个小区域的处理结果合并成完整的图像结果。

二.分治算法的应用实例

1.快速排序(Quick Sort

  • 分解
    • 快速排序选择一个基准元素,将数组分成两部分,一部分的元素都小于基准元素,另一部分的元素都大于基准元素。
  • 解决
    • 对这两部分子数组递归地进行快速排序。
  • 合并
    • 由于快速排序是原地排序,不需要额外的合并操作,子数组排序完成后,整个数组也就排序完成了。

接下来通过例题来了解如何使用快速排序。

1.颜色划分

题目

在这里插入图片描述

算法原理

为了更好地理解分治思想快速排序,建议先理解颜色划分这道题,后面的快速排序就是再次基础上进行递归操作。

在这里插入图片描述

代码实现

void sortColors(vector<int>& nums){//三个指针,left指针指向0区间最右侧,right指针指向2区间最左侧int left = -1, right = nums.size();//i指针用来遍历数组int i = 0;while(i<right){if(nums[i]==0){swap(nums[++left], nums[i++]);}else if(nums[i]==2){swap(nums[--right], nums[i]);}else{i++;}}
}

2.排序数组

题目

在这里插入图片描述

算法原理

在这里插入图片描述

代码实现

//随机取基准值
int getkey(int left,int right,vector<int>& nums){int r = rand();return nums[r % (right - left + 1) + left];
}
//分治递归快速排序
void quicksort(int l,int r,vector<int>& nums){if(l>=r){return;}int key = getkey(l, r, nums);int left = l - 1, right = r + 1;int i = l;while(i<right){if(nums[i]<key){swap(nums[++left], nums[i++]);}else if(nums[i]>key){swap(nums[--right], nums[i]);}else{i++;}}quicksort(l, left, nums);quicksort(right, r, nums);
}
vector<int> sortArray(vector<int>& nums){srand(time(NULL));quicksort(0, nums.size()-1, nums);return nums;
}

3.数组中的第K个最大元素

题目

在这里插入图片描述

算法原理

在这里插入图片描述

代码实现

//分治递归快速选择
int quicksort(int l,int r,vector<int>&nums,int k){if(l==r){return nums[l];}int key = getkey(l, r, nums);int left = l - 1, right = r + 1;int i = l;while(i<right){if(nums[i]<key){swap(nums[++left], nums[i++]);}else if(nums[i]>key){swap(nums[--right], nums[i]);}else{i++;}}//c是大于基准值区间的个数int c = r - right + 1;//b是等于基准值区间的个数int b = right - left - 1;//如果k小于c,则递归到大于基准值的子区间查找if(c>=k){return quicksort(right, r, nums, k);}//如果k小于b+c,则直接返回基准值else if((c+b)>=k){return key;}//都不满足,则递归到小于基准值的子区间查找else{return quicksort(l, left, nums, k - b - c);}
}
int findKthLargest(vector<int>& nums, int k){srand(time(NULL));return quicksort(0, nums.size() - 1, nums,k);
}

4.最小的k个数

题目

在这里插入图片描述

算法原理

本道题也属于topK问题,和上面的题是类似题型,不同的是,上一道题是排升序从后往前找第K个最大元素;而本题中同样也可以排升序,但是是从前往后找前K个最小的数,因此这里就要查找小于基准值区间的个数和等于基准值区间的个数,然后比较K值。

代码实现

//分治递归快速选择
void _quicksort(int l,int r,vector<int>& nums,int k){if(l==r){return;}int key = getkey(l, r, nums);int left = l - 1, right = r + 1;int i = l;while(i<right){if(nums[i]<key){swap(nums[++left], nums[i++]);}else if(nums[i]>key){swap(nums[--right], nums[i]);}else{i++;}}//a表示小于基准值区间的个数int a = left - l + 1;//b表示等于基准值区间的个数int b = right - left - 1;if(a>=k){//当小于基准值区间的值大于等于K个数时,继续到对应的子区间递归查找quicksort(l, left, nums, k);}else if((a+b)>=k){//当小于等于基准值区间的值大于等于k个数时,直接结束返回return;}else{//当上面两种情况都不满足时,继续到大于基准值区间中查找k-a-b个数quicksort(right, r, nums, k);}}
vector<int> smallestK(vector<int>& arr, int k){srand(time(NULL));_quicksort(0, arr.size() - 1, arr, k);return (arr.begin(), arr.begin() + k);
}

2.归并排序(Merge Sort

  • 分解
    • 归并排序将待排序的数组不断的分成两半,知道每个子数组只有一个元素。
  • 解决
    • 当子数组只有一个元素时,他就是有序的。对于其他子数组,递归地调用归并排序进行排序。
  • 合并
    • 采用合并操作,将两个有序的子数组合并成一个有序数组。例如,有两个有序子数组[1,3,5]和[2,4,6],通过比较元素大小,逐步地合并得到[1,2,3,4,5,6]。

接下来通过几道例题来讲解如何使用归并排序。

1.排序数组

题目

在这里插入图片描述

算法原理

第一道题就是练习如何使用归并排序,这里可以直接看我之前的关于排序算法二的博客中的归并排序,里面有详细的讲解归并排序,这里直接展示代码实现。

一定要先理解本道题之后再尝试写下面的三道题,下面的题都是在此基础上的变形。

代码实现

void mergesort(int left,int right,vector<int>& nums){//区间不存在,直接结束返回if(left>=right){return;}//1.分区间int mid = (right + left) / 2;//[left,mid],[mid+1,right]//2.递归子区间mergesort(left, mid, nums);mergesort(mid + 1, right, nums);//3.排序int cur1 = left, cur2 = mid + 1;int i = 0;//辅助数组vector<int> tmp(right - left + 1);while(cur1<=mid&&cur2<=right){tmp[i++] = (nums[cur1] <= nums[cur2]) ? nums[cur1++] : nums[cur2++];}while(cur1<=mid){tmp[i++] = nums[cur1++];}while(cur2<=right){tmp[i++] = nums[cur2++];}//4.还原for (int i = left;i<=right;i++){nums[i] = tmp[i - left];}
}
vector<int> sortArray(vector<int>& nums){mergesort(0, nums.size() - 1, nums);return nums;
}

2.交易逆序对的总数

题目

在这里插入图片描述

算法原理

在这里插入图片描述

代码实现

//通过归并排序查找逆序对数
int mergesort1(int left,int right,vector<int>& nums){//区间不存在,直接结束返回0if(left>=right){return 0;}//1.分区间 [left,mid]  [mid+1,right]int mid = (left + right) / 2;//2.递归左右子区间int Lret = mergesort1(left, mid, nums);int Rret = mergesort1(mid + 1, right, nums);//3.排序+左右查找逆序对个数int cur1 = left, cur2 = mid + 1;int i = 0;int ret = 0;vector<int> tmp(right - left + 1);//升序,找出该数之前有多少个比自己大的while(cur1<=mid&&cur2<=right){if(nums[cur1]>nums[cur2]){ret += mid - cur1 + 1;tmp[i++] = nums[cur2++];}else{tmp[i++] = nums[cur1++];}}/*//降序,找出该数之后有多少个比自己小的while(cur1<=mid&&cur2<=right){if(nums[cur1]>nums[cur2]){ret += right - cur2 + 1;tmp[i++] = nums[cur1++];}else{tmp[i++] = nums[cur2++];}}*/while(cur1<=mid){tmp[i++] = nums[cur1++];}while(cur2<=right){tmp[i++] = nums[cur2++];}//4.还原for (int i = left; i <= right;i++){nums[i] = tmp[i - left];}return ret + Lret + Rret;
}int reversePairs(vector<int>& record){return mergesort1(0, record.size() - 1, record);
}

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

题目

在这里插入图片描述

算法原理

这道题和上一道逆序对相似,仔细看可以发现不同点,上一题是查找数组中所有地逆序对总数,而这道题则是查找数组中每一个元素的逆序对个数并用数组输出结果,因此,这道题的找逆序对大致思路还是和上一题一样,只不过这次不在累加,而是单独存放。

本道题的重点就是,如何使输出的逆序对个数一一对应原始数组中的位置?

我刚开始想到的是通过哈希表建立原始数组中的元素和逆序对个数的映射关系,通过当前数组元素来找到逆序对个数,最后再通过哈希表中的值拷贝到统计数组中。但是最后发现,这里忽略了一个问题,就是如果存在相同元素时,就会导致两个相同元素的逆序对个数累加,比如第一个10的逆序对个数是5,而第二个10的逆序对个数是1,使用哈希表就会导致,两个10的逆序对个数都是6(5+1)。

因此在这之后,我又从新想到一种方法,直接建立一个新的数组,里面存放的是一个键值对,一个值表示原始数组中的值,用来进行排序;而另一个值用来表示元素在原始数组中的下标,这样在访问当前元素时就可以直接找到下标进而修改统计数组中对应的逆序对个数。

注意这里还有一个注意点就是,在递归函数中,因为使用的是新创建的键值对数组,因此辅助数组tmp也要变成键值对数组,这样在排序时才不会导致下标的丢失。

代码实现

//归并统计每个数
void mergesort2(int left,int right,vector<pair<int,int>>& nums,vector<int>& counts){//区间不存在,直接结束返回if(left>=right){return;}//1.分区间int mid = (left + right) / 2;//2.递归左右子区间mergesort2(left, mid, nums, counts);mergesort2(mid + 1, right, nums, counts);//3.排降序,统计个数int cur1 = left, cur2 = mid + 1;int i = 0;vector<pair<int,int>> tmp(right - left + 1);while(cur1<=mid&&cur2<=right){if(nums[cur1].first>nums[cur2].first){counts[nums[cur1].second] += right - cur2 + 1;tmp[i].first = nums[cur1].first;tmp[i++].second = nums[cur1++].second;}else{tmp[i].first = nums[cur2].first;tmp[i++].second = nums[cur2++].second;}}while(cur1<=mid){tmp[i].first = nums[cur1].first;tmp[i++].second = nums[cur1++].second;}while(cur2<=right){tmp[i].first = nums[cur2].first;tmp[i++].second = nums[cur2++].second;}//还原for(int i=left;i<=right;i++){nums[i].first = tmp[i - left].first;nums[i].second = tmp[i - left].second;}
}
vector<int> countSmaller(vector<int>& nums){//注意这里不能用哈希表建立元素和下标的映射关系,因为可能存在相同的元素//使用哈希表会导致相同的元素统计个数相加//重新建立一个数组,存放原数组中的元素以及对应的下标vector<pair<int, int>> newnums(nums.size());for(int i=0;i<nums.size();i++){newnums[i].first = nums[i];newnums[i].second = i;}//通过下标找到统计数组中对应元素的位置vector<int> counts(nums.size());mergesort2(0, newnums.size()-1, newnums, counts);return counts;
}

4.翻转对

题目

在这里插入图片描述

算法原理

本道题也是逆序对的变形,不同的是,这次不再是直接通过归并排序的性质就能直接找到,因此,本道题要在原本的归并排序基础上加上查找符合要求的个数,具体的查找方法就是通过双指针来实现。

因为归并排序在排序前(本道题是降序),左子区间是降序,右子区间也是降序,我们需要找到左子区间中是否存在值的二分之一大于右子区间的值,直接通过双指针来实现查找即可,查找完之后再进行左右子区间的归并排序。

代码实现

int mergesort3(int left,int right,vector<int>& nums){//区间不存在,直接结束返回0if(left>=right){return 0;}//1.分区间int mid = (left + right) / 2;//2.递归左右子区间int Lret=mergesort3(left,mid,nums);int Rret = mergesort3(mid + 1, right, nums);//3.排序,统计符合的个数int ret = 0;int cur1 = left, cur2 = mid + 1;int i = 0;vector<int> tmp(right - left+1);//重点,通过双指针找到符合要求的个数while(cur1<=mid){while(cur2<=right&&nums[cur1]/2.0<=nums[cur2]){cur2++;}if(cur2>right){break;}ret += right - cur2 + 1;cur1++;}while(cur1<=mid&&cur2<=right){tmp[i++] = nums[cur1] > nums[cur2] ? nums[cur1++] : nums[cur2++];}while(cur1<=mid){tmp[i++] = nums[cur1++];}while(cur2<=right){tmp[i++] = nums[cur2++];}//4.还原for(int i=left;i<=right;i++){nums[i] = tmp[i - left];}return ret + Lret + Rret;
}
int reversePairs1(vector<int>& nums){return mergesort3(0, nums.size() - 1, nums);
}

以上就是关于分治算法中快速排序和归并排序的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/67218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenAI Whisper:语音识别技术的革新者—深入架构与参数

当下语音识别技术正以前所未有的速度发展&#xff0c;极大地推动了人机交互的便利性和效率。OpenAI的Whisper系统无疑是这一领域的佼佼者&#xff0c;它凭借其卓越的性能、广泛的适用性和创新的技术架构&#xff0c;正在重新定义语音转文本技术的规则。今天我们一起了解一下Whi…

python+playwright自动化测试(一):安装及简单使用,截图录屏

目录 基本使用 浏览器调用 启用浏览器 创建窗口对象 访问URL 页面的刷新、返回、前进 关闭 截图、录屏、保存pdf 截图 录屏 保存为pdf 设置窗口大小 调试模式 手机模式及new_context的更多参数 手机模式 new_context的其他参数 设置语言和时区 设置和修改位置…

初识C++(二)

六、引用 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它引用的变量共用同一块内存空间。 通俗地讲&#xff0c;可以理解为一个人能够拥有多个称呼&#xff0c;这些所有的称呼都是表示这一…

【RedisStack】Linux安装指南

【RedisStack】Linux安装指南.md 前言下载解压创建启动文件设置密码把密码设置到环境变量启动/停止相关命令测试&验证官网资料参考资料 前言 Redis Stack是使用Redis的最佳起点。我们将我们必须提供的最好的技术捆绑在一起&#xff0c;形成一个易于使用的软件包。Redis St…

达梦8-DMSQL程序设计学习笔记1-DMSQL程序简介

1、DMSQL程序简介 DMSQL程序是达梦数据库对标准SQL语言的扩展&#xff0c;是一种过程化SQL语言。在DMSQL程序中&#xff0c;包括一整套数据类型、条件结构、循环结构和异常处理结构等&#xff0c;DMSQL程序中可以执行SQL语句&#xff0c;SQL语句中也可以使用DMSQL函数。 DMSQ…

STM32 FreeRTOS 基础知识

多任务处理 内核是操作系统的核心组件。诸如 Linux 这样的操作系统采用的内核&#xff0c; 看似允许用户同时访问计算机。很明显&#xff0c;多个用户可以同时执行多个程序。 每个执行程序都是受操作系统控制的任务&#xff08;或线程&#xff09;。如果一个操作系统能够以这…

T-SQL编程

目录 1、T-SQL的元素 1.1 标识符 1. 常规标识符 2. 分隔标识符 1.2 变量 1. 全局变量 2. 局部变量 1.3 运算符 1. 算数运算符 2. 赋值运算符 3. 位运算符 4. 比较运算符 5. 逻辑运算符 6. 字符串连接运算符 7. 一元运算符 8. 运算符的优先级和结合性 1.4 批处…

js中的Object.defineProperty()详解

文章目录 一、Object.defineProperty()二、descriptor属性描述符2.1、数据描述符2.2、访问器描述符2.3、descriptor属性2.3.1、value2.3.2、writable2.3.3、enumerable &#xff08;可遍历性&#xff09;2.3.4、configurable &#xff08;可配置性&#xff09; 三、注意事项 一…

【搭建JavaEE】(2)Tomcat安装配置和第一个JavaEE程序

Tomcat–容器(Container) 下载 Apache Tomcat - Welcome! 下载完成 请求/响应 结构 测试 查看Jdk版本 改端口号localhost8080–>8099 学学人家以后牛逼了可以用自己名字当文件夹名 配置端口8099 找到server文件 用记事本打开 再打开另一个logging文件 ”乱码解决“步骤&…

centos7.6 安装nginx 1.21.3与配置ssl

1 安装依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel2 下载Nginx wget http://nginx.org/download/nginx-1.21.3.tar.gz3 安装目录 mkdir -p /data/apps/nginx4 安装 4.1 创建用户 创建用户nginx使用的nginx用户。 #添加www组 # groupa…

高级软件工程-复习

高级软件工程复习 坐标国科大&#xff0c;下面是老师说的考试重点。 Ruby编程语言的一些特征需要了解要能读得懂Ruby程序Git的基本命令操作知道Rails的MVC工作机理需要清楚&#xff0c;Model, Controller, View各司什么职责明白BDD的User Story需要会写&#xff0c;SMART要求能…

TrollFools 2.10-22 插件注入工具 官方版

《TrollFools巨魔设备专用插件注入工具》这是一款专为巨魔设备打造的插件注入神器&#xff0c;功能强大且操作便捷。它能够轻松地将插件注入通过AppStore商店下载的任意APP中&#xff0c;同时也能随时卸载&#xff0c;丝毫不影响APP的正常使用。注入后的APP仍可正常更新&#x…

30分钟内搭建一个全能轻量级springboot 3.4 + 脚手架 <1> 5分钟快速创建一个springboot web项目

快速导航 <1> 5分钟快速创建一个springboot web项目 <2> 5分钟集成好最新版本的开源swagger ui&#xff0c;并使用ui操作调用接口 <3> 5分钟集成好druid并使用druid自带监控工具监控sql请求 <4> 5分钟集成好mybatisplus并使用mybatisplus generator自…

arcgis中生成格网矢量带高度

效果 1、数据准备 (1)矢量边界(miain.shp) (2)DEM(用于提取格网标高) (3)DSM(用于提取格网最高点) 2、根据矢量范围生成格网 模板范围选择矢量边界,像元宽度和高度根据坐标系来输入,我这边是4326的,所以输入的是弧度,输出格网矢量gewang.shp 3、分区统计 …

海豚调度DolphinScheduler-3.1.9配置windows本地开发环境

源代码下载地址https://dolphinscheduler.apache.org/zh-cn/docs/3.1.9 1.Zookeeper安装与使用 如图下载解压zookeeper安装包&#xff0c;并创建data和log目录 下载地址 https://archive.apache.org/dist/zookeeper/zookeeper-3.6.4/apache-zookeeper-3.6.4-bin.tar.gz 进入…

P1图文解析:初识算法和数据结构

文章目录 前言1、算法例子1.1、查字典&#xff08;二分查找算法&#xff09;1.2、整理扑克&#xff08;插入排序算法&#xff09;1.3、货币找零&#xff08;贪心算法&#xff09; 2、算法与数据结构2.1、算法定义2.2、数据结构定义2.3、数据结构与算法的关系2.4、独立于编程语言…

校园跑腿小程序---轮播图,导航栏开发

hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生…

UE材质节点Fresnel

Fresnel节点 ExponentIn 控制边缘透明度 BaseReflectFractionIn 控制中心透明度

浅谈云计算07 | 云安全机制

浅谈云计算安全机制&#xff1a;全方位守护云端世界 一、引言二、加密技术&#xff1a;数据的隐形护盾三、散列机制&#xff1a;数据完整性的忠诚卫士四、数字签名&#xff1a;数据来源与真伪的鉴定专家五、公钥基础设施&#xff08;PKI&#xff09;&#xff1a;信任的基石六、…

Notepad++上NppFTP插件的安装和使用教程

一、NppFTP插件下载 图示是已经安装好了插件。 在搜索框里面搜NppFTP&#xff0c;一般情况下&#xff0c;自带的下载地址容易下载失败。这里准备了一个下载连接&#xff1a;Release v0.29.10 ashkulz/NppFTP GitHub 这里我下载的是x86版本 下载好后在nodepad的插件里面选择打…