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

算法 --- 分治(快排)

分治(快排)

分治(以快排为代表)算法最适合解决“将复杂问题分解后,子问题的解能直接合并为原问题解”的题目类型。


详细说明:

分治法的核心思想是“分而治之”,它通过以下三个步骤解决问题:

  1. 分(Divide):将原问题分解成若干个规模较小的子问题。

  2. 治(Conquer):递归地解决这些子问题。如果子问题规模足够小,则直接求解。

  3. 合(Combine):将子问题的解合并,形成原问题的解。

适用于分治法的题目类型和特征:
  1. 问题可分解性

    • 问题必须能够被自然地分解为几个规模更小的、结构相同的子问题。

    • 典型例子:归并排序、快速排序。一个大的无序数组可以分解为两个小的无序数组进行排序。

  2. 子问题独立性

    • 各子问题之间相互独立,没有重叠(或者重叠很少)。这是分治与动态规划的关键区别(动态规划的子问题有重叠)。

    • 典型例子:快速排序。划分后的左右两个子数组的排序过程互不干扰。

  3. 解可合并性

    • 这是最关键的一点。必须能够高效地将子问题的解合并为原问题的解。如果合并步骤非常复杂,那么分治法可能不是最佳选择。

    • 典型例子

      • 归并排序:合并两个已排序的子数组为一个有序数组的操作非常高效(O(n))。

      • 快速排序:其“合并”步骤实际上什么都不用做,因为经过“分”和“治”之后,整个数组已经自然有序了。这是快排比归并排序更高效的原因之一。

什么题目具体适用于分治(快排)?

快排是分治法最经典的应用,但它本身是一种排序算法。更广泛地说,分治法适用于以下几类问题:

  • 排序与选择问题

    • 快速排序:通过一个基准值(pivot)将数组“分”为左右两部分,递归“治”之,无需“合”。

    • 归并排序:将数组平分为两部分,递归排序后,再合并。

    • 寻找第K大/小元素(快速选择算法):思路类似快排,每次划分后只在包含目标的那一部分递归,平均时间复杂度为 O(n)。

  • 计算问题

    • 大整数乘法:将大整数分解为更小的部分进行计算,再合并结果。

    • 矩阵乘法(Strassen算法):将大矩阵分块,用更少的乘法次数完成计算。

  • 几何问题

    • 最近点对问题:将平面上的点集按x坐标分成两半,分别递归求解两半中的最近点对,再合并处理跨越两部分的点对。

  • 数据结构操作

    • 线段树、归并树的构建和查询,其本质就是分治思想。

总结:

当你遇到一个问题时,可以问自己这三个问题来判断是否适用分治:

  1. 我能把它分成几个一样的、更小的问题吗?(可分

  2. 这些小问题可以独立解决吗?(独立

  3. 解决完小问题后,我能很容易地把它们的答案组合成最终答案吗?(可合

如果答案都是“是”,那么分治法就很可能是你的得力工具。快排正是完美符合这三点的典范。

题目练习

75. 颜色分类 - 力扣(LeetCode)

解法(快排思想 - 三指针法使数组分三块):

算法思路:

类比数组分两块的算法思想,这里是将数组分成三块,那么我们可以再添加一个指针,实现数组分三块。

设数组大小为 n,定义三个指针 leftcurright

  • left:用来标记 0 序列的末尾,因此初始化为 -1

  • cur:用来扫描数组,初始化为 0

  • right:用来标记 2 序列的起始位置,因此初始化为 n

cur 往后扫描的过程中,保证:

  • [0, left] 内的元素都是 0;

  • [left + 1, cur - 1] 内的元素都是 1;

  • [cur, right - 1] 内的元素是待定元素;

  • [right, n] 内的元素都是 2。

算法流程:

a. 初始化 cur = 0left = -1right = numsSize

b. 当 cur < right 的时候(因为 right 表示的是 2 序列的左边界,因此当 cur 碰到 right 的时候,说明已经将所有数据扫描完毕了),一直进行下面循环:

根据 nums[cur] 的值,可以分为下面三种情况:

i. nums[cur] == 0;说明此时这个位置的元素需要在 left + 1 的位置,因此交换 left + 1cur 位置的元素,并且让 left++(指向 0 序列的边界),cur++(为什么可以 ++ 呢,是因为 left + 1 位置要么是 0,要么是 cur,交换完毕之后,这个位置的值已经符合我们的要求,因此 cur++);

ii. nums[cur] == 1;说明这个位置应该在 leftcur 之间,此时无需交换,直接让 cur++,判断下一个元素即可;

iii. nums[cur] == 2;说明这个位置的元素应该在 right - 1 的位置,因此交换 right - 1cur 位置的元素,并且让 right--(指向 2 序列的边界),cur 不变(因为交换过来的数是没有被判断过的,因此需要在下轮循环中判断)

c. 当循环结束之后:

  • [0, left] 表示 0 序列;

  • [left + 1, right - 1] 表示 1 序列;

  • [right, numsSize - 1] 表示 2 序列。

left 一点是要从-1开始的,因为还没有确定下标0位置是否为0!

class Solution {
public:void sortColors(vector<int>& nums) {int left = -1, cur = 0, right = nums.size();while(cur < right){if(nums[cur] == 0) swap(nums[++left], nums[cur++]);else if(nums[cur] == 1) cur++;else swap(nums[--right], nums[cur]);}}
};

912. 排序数组 - 力扣(LeetCode)

解法(数组分三块思想 + 随机选择基准元素的快速排序):

算法思路:

我们在数据结构阶段学习的快速排序的思想可以知道,快排最核心的一步就是 Partition(分割数据):将数据按照一个标准,分成左右两部分。

如果我们使用荷兰国旗问题的思想,将数组划分为 左中右 三部分:左边是比基准元素小的数据,中间是与基准元素相同的数据,右边是比基准元素大的数据。然后再去递归的排序左边部分和右边部分即可(可以舍去大量的中间部分)。

在处理数据量有很多重复的情况下,效率会大大提升。

算法流程:

随机选择基准算法流程:

函数设计:int randomKey(vector<int>& nums, int left, int right)

a. 在主函数那里种一颗随机数种子;

b. 在随机选择基准函数这里生成一个随机数;

c. 由于我们要随机产生一个基准,因此可以将随机数转换成随机下标:让随机数 % 上区间大小,然后加上区间的左边界即可。

快速排序算法主要流程:

a. 定义递归出口;

b. 利用随机选择基准函数生成一个基准元素;

c. 利用荷兰国旗思想将数组划分成三个区域;

d. 递归处理左边区域和右边区域。

class Solution {
public:int getRandom(vector<int>& nums, int l, int r){int tmp = rand();return nums[tmp % (r - l + 1) + l];}void qsort(vector<int>& nums, int l, int r){if(l >= r) return;int key = getRandom(nums, l, r);int cur = l, left = l - 1, right = r + 1;while(cur < right){if(nums[cur] < key) swap(nums[++left], nums[cur++]);else if(nums[cur] == key) cur++;else swap(nums[--right], nums[cur]);}// [l, left] [left + 1, right - 1] [right, r]qsort(nums, l, left);qsort(nums, right, r);}vector<int> sortArray(vector<int>& nums) {srand(time(NULL));qsort(nums, 0, nums.size() - 1);return nums; }
};

215. 数组中的第K个最大元素 - 力扣(LeetCode)(重要)

解法(快速选择算法):O(N)!!!

算法思路:

在快排中,当我们把数组「分成三块」之后:[l, left] [left + 1, right - 1] [right, r],我们可以通过计算每一个区间内元素的「个数」,进而推断出我们要找的元素是在「哪一个区间」里面。

那么我们可以直接去「相应的区间」去寻找最终结果就好了。

class Solution {
public:int findKthLargest(vector<int>& nums, int k) {srand(time(NULL));return qsort(nums, 0, nums.size() - 1, k);}int qsort(vector<int>& nums, int l, int r, int k){if(l == r) return nums[l];int key = getRandom(nums, l, r);int left = l - 1, cur = l, right = r + 1;while(cur < right){if(nums[cur] < key) swap(nums[++left], nums[cur++]);else if(nums[cur] == key) cur++;else swap(nums[--right], nums[cur]);} int c = r - right + 1;int b = right - left - 1;if(c >= k) return qsort(nums, right, r, k);else if(b + c >= k) return key;else return qsort(nums, l, left, k - b - c);}int getRandom(vector<int>& nums, int left, int right){return nums[rand() % (right - left + 1) + left];}
};

LCR 159. 库存管理 III - 力扣(LeetCode)

解法(快速选择算法):

算法思路:

在快排中,当我们把数组「分成三块」之后:[l, left] [left + 1, right - 1] [right, r],我们可以通过计算每一个区间内元素的「个数」,进而推断出最小的 k 个数在哪些区间里面。

那么我们可以直接去「相应的区间」继续划分数组即可。

class Solution {
public:vector<int> inventoryManagement(vector<int>& nums, int k) {srand(time(NULL));qsort(nums, 0, nums.size() - 1, k);return {nums.begin(),nums.begin() + k};}void qsort(vector<int>& nums, int l, int r, int k){if(l >= r) return;int key = getRandom(nums, l, r);int left = l - 1, right = r + 1, cur = l;while(cur < right){if(nums[cur] < key) swap(nums[++left], nums[cur++]);else if(nums[cur] == key) cur++;else swap(nums[--right], nums[cur]);}int a = left - l + 1;int b = right - left -1;if(a > k) return qsort(nums, l, left, k);else if(a + b >= k) return;else return qsort(nums, right, r, k - a - b);} int getRandom(vector<int>& nums, int left, int right){return nums[rand() % (right- left + 1) + left];}
};

http://www.dtcms.com/a/365540.html

相关文章:

  • 机器学习在Backtrader多因子模型中的应用
  • 2025年大学必考的十大计算机专业证书推荐:解锁你的职业未来!
  • 从0到1:解锁“预训练+微调”的AI魔法密码
  • 如何解决虚拟机网络连接问题:配置固定 IP 篇
  • 精密板料矫平机:把“皱巴巴”的金属熨成镜面
  • k8s,v1.30.4,安装使用docker
  • java面试中经常会问到的spring问题有哪些(基础版)
  • 日志打印--idf的esp32
  • 如何区分 Context Engineering 与 Prompt Engineering
  • 用AI做旅游攻略,真能比人肉整理靠谱?
  • 特斯拉“宏图计划4.0”发布!马斯克:未来80%价值来自机器人
  • Springboot3+SpringSecurity6Oauth2+vue3前后端分离认证授权-客户端
  • C++:类和对象(上)
  • 集成运算放大器的作用、选型和测量指南-超简单解读
  • 夸克网盘辅助工具 QuarkPanTool 分析
  • 代码随想录算法训练营第一天 || (双指针)27.移除元素 26.删除有序数组中的重复项 283.移动零 977.有序数组的平方
  • 从 “能说会道” 到 “能做会干”:AI Agent 技术突破,如何让人工智能拥有 “行动力”?
  • Linux 创建服务 使用systemctl 管理
  • uni app 的app端 写入运行日志到指定文件夹。
  • 腾讯云《意愿核身移动 H5》 快速完成身份验证接入
  • 国产CAD皇冠CAD(CrownCAD)建模教程:汽车驱动桥
  • HTML5 标题标签、段落、换行和水平线
  • shell-awk命令详解(理论+实战)
  • 【面试场景题】1GB 大小HashMap在put时遇到扩容的过程
  • 第七章 表达:成果展示--创建第二大脑读书笔记
  • 10名机械画图人员如何共享一台云服务器的软硬件资源进行设计办公
  • ArcGIS解决csv或者excel转换为矢量的坐标问题
  • 第二章 Windows 核心概念通俗解析
  • 03 - HTML常用标签
  • 【学Python自动化】 9.1 Python 与 Rust 类机制对比学习笔记