• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
FengFlyYing
博客园    首页    新随笔    联系   管理    订阅  订阅

算法第二章实践作业

算法第二章实践作业:找第k小的数的分治算法学习记录

我对着“分治”两个字琢磨了好久,翻了课本又看了好几个例题,才慢慢理出点思路。下面就记录一下我磕磕绊绊的学习过程,肯定有不严谨的地方,还请老师指点。

一、找第k小的数的分治算法思路

分治法更高效,分治的核心是“分而治之”——把大问题拆成小问题,解决小问题后再合并结果,这样能减少不必要的计算。

这个算法的关键步骤是“选主元”和“划分”,跟快排的思路有点像,但比快排更“偷懒”,因为不用全排序。我试着用自然语言捋了一遍流程:

  1. 选主元:从要处理的数组里随便挑一个数当“主元”(此处选中间)。

  2. 划分数组:把数组分成三部分——比主元小的数(左区间)、等于主元的数(中间区间)、比主元大的数(右区间)。这一步跟快排的划分差不多,就是遍历数组把元素往对应的区间放。

  3. 判断第k小的数在哪:计算左区间的元素个数叫left_len,中间区间的叫mid_len。如果k ≤ left_len,说明第k小的数在左区间里,直接去左区间找第k小的数就行;如果left_len < k ≤ left_len + mid_len,那主元就是第k小的数(因为中间全是等于主元的数);要是k > left_len + mid_len,就去右区间找,不过这时候要找的是右区间里第k - left_len - mid_len小的数了。

  4. 递归终止:当数组里只有一个元素的时候,这个元素就是要找的数(因为这时候k肯定是1了)。

基于这个思路,我写了个伪代码

函数 findKthSmallest(arr, k):
// 递归终止条件:数组只有一个元素,直接返回
if 数组arr的长度 == 1:
返回 arr[0]
// 步骤1:随机选主元(这里简化成选数组中间位置的元素,实际随机更好)
主元位置 = 数组arr的长度 // 2 (//表示整除)
主元 = arr[主元位置]
// 步骤2:划分成左、中、右三个区间
左区间left = 空数组
中间区间mid = 空数组
右区间right = 空数组
遍历数组arr中的每个元素x:
if x < 主元:
把x加入left
elif x == 主元:
把x加入mid
else:
把x加入right
// 步骤3:判断k所在区间,递归查找
left_len = left的长度
mid_len = mid的长度
if k <= left_len:
// 第k小的数在左区间,递归找左区间的第k小
返回 findKthSmallest(left, k)
elif k <= left_len + mid_len:
// 第k小的数就是主元
返回 主元
else:
// 第k小的数在右区间,递归找右区间的第k - left_len - mid_len小
返回 findKthSmallest(right, k - left_len - mid_len)

二、算法的时间复杂度分析

时间复杂度这块我一直有点晕,查了好久才勉强搞懂大概思路。时间复杂度主要看划分后每次处理的数组长度和划分的效率。

  1. 最好时间复杂度

最好的情况应该是每次选的主元都特别“合适”,刚好能把数组分成差不多相等的两部分。比如每次划分后,左区间和右区间的长度都不超过原来的一半。这时候每次递归处理的数组长度都是上一次的1/2,而划分一次数组的时间是O(n)(遍历一遍数组)。

推导:假设数组长度是n,最好情况下的时间复杂度是T(n)。每次划分后处理的子问题长度是n/2,T(n) = T(n/2) + O(n)。递推几次:T(n) = T(n/2) + n = T(n/4) + n/2 + n = T(n/8) + n/4 + n/2 + n = ... 一直到T(1)的时候,加起来的和是n + n/2 + n/4 + ... + 1 ≈ 2n,所以最好时间复杂度是O(n)。

  1. 最坏时间复杂度

每次选的主元都是当前数组里最小或者最大的数。比如数组本来就是升序的,每次还偏偏选第一个元素当主元,那划分的时候左区间就为空,所有元素都在右区间(除了主元自己)。这时候每次递归处理的数组长度只比原来少1,相当于没怎么“分”,跟遍历差不多了。

这时候时间复杂度T(n) = T(n-1) + O(n)。T(n) = T(n-1) + n = T(n-2) + (n-1) + n = ... = T(1) + 2 + 3 + ... + n ≈ n²/2,所以最坏时间复杂度是O(n²)。

三、对分治法的体会和思考

学完分治法这一章,我最大的感受是:分治法不是一种具体的算法,而是一种“解决问题的思路”,核心就是“拆大问题、解小问题、合结果”。以前我做题总想着“一步到位”,比如找第k小的数就先排序,现在发现有时候把问题拆开来,反而能省很多事。

分治法的关键在于“怎么分”和“怎么治”。“分”的时候要尽量把问题拆成规模差不多的子问题,这样递归下去效率才高,就像找第k小的数里选主元一样,选得好分的均匀,效率就上去了,如果分的不均匀,比如最坏情况那样,效率很不好。“治”的时候就是处理子问题,这部分往往很简单,比如递归到数组只有一个元素的时候,直接返回就行。还有个重要的点是“合并”,不过这次找第k小的数好像没怎么用到合并,因为划分后直接就能确定答案在哪个子区间,不用把所有子问题的结果合起来。

递归是实现分治法的常用工具,但分治法不一定非要用递归。但是用递归写分治法很简洁,不用自己写循环去控制规模。但递归也有缺点,比如如果递归层数太多,可能会栈溢出。

分治法其实有点“化繁为简”的味道。比如面对一个n规模的大问题,我们可能不知道怎么下手,但如果把它拆成n/2规模的小问题,可能就觉得熟悉了,再拆到1规模的时候,就完全没问题了。这种思路不仅在算法里有用,平时做题或者解决生活里的问题好像也能用到,比如把一个大任务拆成几个小任务,逐个完成就轻松多了。

当然我现在对分治法的理解还很肤浅,许多问题还没搞太懂,以后还要多做题多琢磨。不过通过这次找第k小的数的实践,至少对分治法的核心思路有了直观的认识。

posted @ 2025-11-02 21:42  FengFlyYing  阅读(4)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3