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

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉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.rhkb.cn/news/1688.html

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

相关文章

Unreal Engine 5 C++ Advanced Action RPG 八章笔记

第八章 Boss Enemy 2-Set Up Boss Character 创建Boss敌人流程 起始的数据UI战斗能力行为树 这集新建Boss敌人的蓝图与动画蓝图和混合空间,看看就行巨人在关卡中,它的影子被打破,更改当前项目中的使用的阴影贴图就可以解决 从虚拟阴影贴图更改为阴影贴图即可 3-Giant Start…

C#,图论与图算法,输出无向图“欧拉路径”的弗勒里(Fleury Algorithm)算法和源程序

1 欧拉路径 欧拉路径是图中每一条边只访问一次的路径。欧拉回路是在同一顶点上开始和结束的欧拉路径。 这里展示一种输出欧拉路径或回路的算法。 以下是Fleury用于打印欧拉轨迹或循环的算法&#xff08;源&#xff09;。 1、确保图形有0个或2个奇数顶点。2、如果有0个奇数顶…

day08_Kafka

文章目录 day08_Kafka课程笔记一、今日课程内容一、消息队列&#xff08;了解&#xff09;**为什么消息队列就像是“数据的快递员”&#xff1f;****实际意义**1、产生背景2、消息队列介绍2.1 常见的消息队列产品2.2 应用场景2.3 消息队列中两种消息模型 二、Kafka的基本介绍1、…

Vue3组件设计模式:高可复用性组件开发实战

Vue3组件设计模式:高可复用性组件开发实战 一、前言 在Vue3中&#xff0c;组件设计和开发是非常重要的&#xff0c;它直接影响到应用的可维护性和可复用性。本文将介绍如何利用Vue3组件设计模式来开发高可复用性的组件&#xff0c;让你的组件更加灵活和易于维护。 二、单一职责…

《使用人工智能心脏磁共振成像筛查和诊断心血管疾病》论文精读

Screening and diagnosis of cardiovascular disease using artificial intelligence-enabled cardiac magnetic resonance imaging 心脏磁共振成像 (CMR) 是心脏功能评估的黄金标准&#xff0c;在诊断心血管疾病 (CVD) 中起着至关重要的作用。然而&#xff0c;由于 CMR 解释的…

幂次进近

数学题。 令n-m^k的绝对值最小&#xff0c;即n-m^k0&#xff0c;此时mn^(1/k)。 据题意要求&#xff0c;m只能取到正整数&#xff0c;那么&#xff0c;n^(1/k)结果恰为整时&#xff0c;其值即为答案&#xff0c;否则&#xff0c;答案为该值临近的两个整数中的一个&#xff0c…

RuoYi-Vue-Plus 加入 GitCode:驱动多租户后台管理创新发展

在当今数字化进程持续推进的时代背景下&#xff0c;企业对后台管理系统的要求不断攀升&#xff0c;高效、安全、灵活与可拓展性成为关键要素。近日&#xff0c;RuoYi-Vue-Plus 正式加入 GitCode&#xff0c;为多租户后台管理领域带来全新动力与机遇&#xff0c;有力推动行业技术…

STM32入门教程-示例程序(按键控制LED光敏传感器控制蜂鸣器)

1. LED Blink&#xff08;闪烁&#xff09; 代码主体包含&#xff1a;LED.c key.c main.c delay.c&#xff08;延时防按键抖动&#xff09; 程序代码如下&#xff08;涉及RCC与GPIO两个外设&#xff09;&#xff1a; 1.使用RCC使能GPIO时钟 RCC_APB2PeriphClockC…

数据挖掘实训:天气数据分析与机器学习模型构建

随着气候变化对各行各业的影响日益加剧&#xff0c;精准的天气预测已经变得尤为重要。降雨预测在日常生活中尤其关键&#xff0c;例如农业、交通和灾害预警等领域。本文将通过机器学习方法&#xff0c;利用历史天气数据预测明天是否会下雨&#xff0c;具体内容包括数据预处理、…

Python在Excel工作表中创建数据透视表

在数据处理和分析工作中&#xff0c;Excel作为一个广泛使用的工具&#xff0c;提供了强大的功能来管理和解析数据。当面对大量复杂的数据集时&#xff0c;为了更高效地总结、分析和展示数据&#xff0c;创建数据透视表成为一种不可或缺的方法。通过使用Python这样的编程语言与E…

SpringBoot配置文件

大家好&#xff0c;我是小帅今天我来学习Web开发中的配置文件。 文章目录 1. 配置⽂件作⽤2. 配置⽂件入门3. 配置⽂件的格式4. properties 配置⽂件说明4.1 properties 基本语法4.2 读取配置⽂件&#xff08;Value 注解&#xff09;4.3 properties 缺点分析 5. yml 配置⽂件说…

FPGA的 基本结构(Xilinx 公司Virtex-II 系列FPGA )

以Xilinx 公司Virtex-II 系列FPGA 为例&#xff0c;其基本结构由下图所示。它是主要由两大部分组成&#xff1a;可编程输入/输出&#xff08;Programmable I/Os&#xff09;部分和内部可配置&#xff08;Configurable Logic&#xff09;部分。 可编程输入/输出&#xff08;I/Os…

day10_Structured Steaming

文章目录 Structured Steaming一、结构化流介绍&#xff08;了解&#xff09;1、有界和无界数据2、基本介绍3、使用三大步骤(掌握)4.回顾sparkSQL的词频统计案例 二、结构化流的编程模型&#xff08;掌握&#xff09;1、数据结构2、读取数据源2.1 File Source2.2 Socket Source…

mybatisPlus(条件构造器API)

文章目录 目录一、mybatisPlus的介绍二、mybatisPlus的基础使用配置BaseMapper的基本CURD&#xff08;增删改查&#xff09; 三、wrapper&#xff08;条件构造器&#xff09;条件构造器&#xff08;wrapper&#xff09;通用API基础条件判断&#xff1a;进阶条件判断&#xff08…

手撕代码: C++实现按位序列化和反序列化

目录 1.需求 2.流程分析 3.实现过程 4.总结 1.需求 在我们正在开发的项目&#xff0c;有这样一种需求&#xff0c;实现固定格式和自由格式的比特流无线传输。解释一下&#xff0c;固定格式形如下面表格&#xff1a; 每个字段都有位宽、类型等属性&#xff0c;这种固定格式一…

【Rust自学】12.6. 使用TDD(测试驱动开发)开发库功能

12.6.0. 写在正文之前 第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print)&#xff0c;是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。 这个项目分为这么几步&#xff1a; 接收命令行参数读取…

C#,入门教程(27)——应用程序(Application)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(26)——数据的基本概念与使用方法https://blog.csdn.net/beijinghorn/article/details/124952589 一、什么是应用程序 Application&#xff1f; 应用程序是编程的结果。一般把代码经过编译&#xff08;等&#xff09;过程&#…

机器学习(1):线性回归概念

1 线性回归基础 1.1 什么是线性 例如&#xff1a;汽车每小时60KM&#xff0c;3小时可以行使多长距离&#xff1f;已知汽车的速度&#xff0c;则汽车的行使距离只与时间唯一相关。在二元的直角坐标系中&#xff0c;描出这一关系的图是一条直线&#xff0c;所以称为线性关系。 线…

日志系统实践

日志系统 产生日志 logging:level:root: infoconfig: /usr/src/config/logback-spring.xml<?xml version"1.0" encoding"UTF-8"?> <configuration><appender name"STDOUT" class"ch.qos.logback.core.ConsoleAppender&qu…

基于微信小程序的智能停车场管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…