第二次作业
一、找第k小的数的分治算法(自然语言+伪代码描述)
该算法核心是模仿快速排序的“分区”思想:通过选一个“基准数”将数组分成两部分,再根据基准数的位置判断第k小数在左半区还是右半区,递归缩小范围直到找到目标。
- 自然语言描述
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个元素)。
- 伪代码描述
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)),递归次数取决于“基准数是否能均匀划分数组”。
- 最好时间复杂度:O(n)
- 条件:每次分区的基准数恰好是当前子数组的“中间值”,能将数组均匀分成两半(左、右半区元素个数接近)。
- 分析:递归深度为O(log n)(类似二分查找),每次递归处理的子数组长度依次为n、n/2、n/4...1,总时间为O(n + n/2 + n/4 + ... + 1) = O(n)(等比数列求和,和为2n-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点关键体会:
- 分治法的“有效性”依赖“问题可拆分+子问题可递归”
- 找第k小数能拆分的关键:通过“分区”将“找整个数组的第k小”转化为“找左/右半区的第k小”,子问题与原问题的逻辑完全一致,无需额外设计新逻辑;
- 反之,若问题无法拆成“同结构子问题”(如判断一个数是否为质数),分治法的优势就无法体现。
- “拆分策略”直接决定算法效率,需避免“极端拆分”
- 找第k小数的最坏复杂度(O(n²)),本质是“拆分策略不佳”(选最值为基准);实际应用中,可通过“随机选基准”或“三数取中(选左、中、右三个数的中位数为基准)”优化,将最坏情况概率降到极低,接近最好复杂度O(n);
- 这说明分治法的效率不是固定的,好的拆分策略能让算法从“低效”变“高效”,是设计分治算法的核心。
- 分治法常与“递归”绑定,但需警惕“递归深度”和“额外空间”
- 递归是实现分治法的简洁方式,但像找第k小数的最坏情况(递归深度O(n)),可能触发“栈溢出”;此时可将递归改为迭代(手动用栈管理子问题),避免栈溢出;
- 此外,分治法的“合并步骤”可能产生额外空间(如归并排序需O(n)临时数组),而找第k小数因无需合并(直接通过基准位置缩小范围),额外空间仅为递归栈的O(log n)(最好情况),体现了“分治法的空间复杂度与是否需要合并密切相关”。
综上,分治法是解决“大规模复杂问题”的高效工具,但需结合问题特性设计“合理的拆分策略”,并平衡好递归深度与空间开销,才能最大化其优势。
浙公网安备 33010602011771号