第二次作业

一、找第k小的数的分治算法(自然语言+伪代码描述)

该算法核心是模仿快速排序的“分区”思想:通过选一个“基准数”将数组分成两部分,再根据基准数的位置判断第k小数在左半区还是右半区,递归缩小范围直到找到目标。

  1. 自然语言描述

1. 终止条件:若待查找的子数组只有1个元素,直接返回该元素(它就是第k小的数)。
2. 分区操作:

  • 从子数组中选一个基准数(如最后一个元素);
  • 遍历子数组,将比基准数小的元素放到左半区,比基准数大的放到右半区,基准数最终落在“分界位置pos”(左半区有pos个元素,基准数是第pos+1小的数)。
    3. 递归缩小范围:
  • 若k = pos+1:基准数就是第k小数,直接返回;
  • 若k < pos+1:第k小数在左半区,递归处理左半区;
  • 若k > pos+1:第k小数在右半区,递归处理右半区(此时需将k更新为k-(pos+1),因为左半区+基准数已有pos+1个元素)。
  1. 伪代码描述

plaintext

Function findKthSmallest(arr, left, right, k):
// arr:待查找数组;left/right:子数组的左右边界;k:目标第k小(1<=k<=right-left+1)
if left == right:
return arr[left] // 子数组只有1个元素,直接返回

// 分区:返回基准数的最终位置pos(左半区元素个数为pos-left)
pos = partition(arr, left, right)
// 计算基准数是当前子数组的第m小(m = 左半区元素数 + 1)
m = pos - left + 1

if k == m:
    return arr[pos]  // 基准数就是第k小
elif k < m:
    // 第k小在左半区,递归处理左半区
    return findKthSmallest(arr, left, pos-1, k)
else:
    // 第k小在右半区,递归处理右半区(更新k为k-m)
    return findKthSmallest(arr, pos+1, right, k - m)

-m 分区辅助函数:选arr[right]为基准,返回基准最终位置
Function partition(arr, left, right):
pivot = arr[right] // 基准数(选最后一个元素)
i = left - 1 // i是左半区的“最后一个元素索引”,初始为空

for j from left to right-1:
    if arr[j] <= pivot:  // 比基准小,加入左半区
        i = i + 1
        swap arr[i] and arr[j]

// 把基准数放到左半区和右半区的分界处
swap arr[i+1] and arr[right]
return i + 1  // 返回基准数的位置

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

时间复杂度的关键是分区操作的效率:每次分区需遍历当前子数组(时间O(n)),递归次数取决于“基准数是否能均匀划分数组”。

  1. 最好时间复杂度:O(n)
  • 条件:每次分区的基准数恰好是当前子数组的“中间值”,能将数组均匀分成两半(左、右半区元素个数接近)。
  • 分析:递归深度为O(log n)(类似二分查找),每次递归处理的子数组长度依次为n、n/2、n/4...1,总时间为O(n + n/2 + n/4 + ... + 1) = O(n)(等比数列求和,和为2n-1,忽略常数项)。
  1. 最坏时间复杂度:O(n²)
  • 条件:每次分区的基准数是当前子数组的“最值”(如数组已升序,选最后一个元素为基准,左半区是整个数组,右半区为空),数组被分成“1个大区间+1个空区间”。
  • 分析:递归深度为O(n)(每次只缩小1个元素的范围),每次递归处理的子数组长度依次为n、n-1、n-2...1,总时间为O(n + (n-1) + (n-2) + ... + 1) = O(n²)(等差数列求和,和为n(n+1)/2)。

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

分治法的核心思想是“分而治之”:将复杂问题拆解成多个“规模更小、结构相同”的子问题,递归解决子问题后,合并结果得到原问题答案。结合找第k小数的算法,可总结出3点关键体会:

  1. 分治法的“有效性”依赖“问题可拆分+子问题可递归”
  • 找第k小数能拆分的关键:通过“分区”将“找整个数组的第k小”转化为“找左/右半区的第k小”,子问题与原问题的逻辑完全一致,无需额外设计新逻辑;
  • 反之,若问题无法拆成“同结构子问题”(如判断一个数是否为质数),分治法的优势就无法体现。
  1. “拆分策略”直接决定算法效率,需避免“极端拆分”
  • 找第k小数的最坏复杂度(O(n²)),本质是“拆分策略不佳”(选最值为基准);实际应用中,可通过“随机选基准”或“三数取中(选左、中、右三个数的中位数为基准)”优化,将最坏情况概率降到极低,接近最好复杂度O(n);
  • 这说明分治法的效率不是固定的,好的拆分策略能让算法从“低效”变“高效”,是设计分治算法的核心。
  1. 分治法常与“递归”绑定,但需警惕“递归深度”和“额外空间”
  • 递归是实现分治法的简洁方式,但像找第k小数的最坏情况(递归深度O(n)),可能触发“栈溢出”;此时可将递归改为迭代(手动用栈管理子问题),避免栈溢出;
  • 此外,分治法的“合并步骤”可能产生额外空间(如归并排序需O(n)临时数组),而找第k小数因无需合并(直接通过基准位置缩小范围),额外空间仅为递归栈的O(log n)(最好情况),体现了“分治法的空间复杂度与是否需要合并密切相关”。

综上,分治法是解决“大规模复杂问题”的高效工具,但需结合问题特性设计“合理的拆分策略”,并平衡好递归深度与空间开销,才能最大化其优势。

posted @ 2025-10-31 17:39  吴秋微  阅读(1)  评论(0)    收藏  举报