最坏情况为线性时间的选择算法之Python实现

本文探讨了如何使用Python实现最坏情况下运行时间为线性的选择算法,该算法来源于《算法导论》第三版第9.3章节。通过详细步骤解析,帮助读者理解并掌握这一高效的选择方法。

算法导论第三版,9.3

import random
import math
#returns the number of elements that smaller than x
#the input is A[p...r] inclusive in the convention of book,  1 <= p <= r <= n
#in Python, to represent A[p...r], we should use a[p-1:r] 
def partition(a,p,r,x):
    low = [m for m in a if m < x]
    high = [m for m in a if m > x]
    a[p-1:r] = low + [x] + high 
    r
### 关于最坏情况 O(n) 时间复杂度的选择算法 #### 选择算法概述 选择算法是一种用于查找数组中第 k 小元素的高效方法。一种常见的实现方式是基于快速排序的思想——分治法,称为 **Quickselect** 算法。然而,标准 Quickselect 的最坏时间复杂度为 \(O(n^2)\)[^1],因为它依赖于每次分区操作的结果。 为了改进这一点并确保最坏情况下仍能达到线性时间复杂度 \(O(n)\),可以采用 **Median of Medians** 方法作为优化策略[^4]。这种方法通过更精确地选取划分点(pivot),使得每一步都能减少一定比例的数据规模,从而达到严格的线性时间复杂度。 --- #### Median of Medians 方法的核心思想 Median of Medians 是一种确定性算法,其目标是从给定数组中选出一个接近中间位置的数作为 pivot,以保证分割后的子问题大小不会过大或过小。具体过程如下: 1. **分组**: 将输入数组分成若干小组,通常每组包含 5 个元素。 2. **求中位数**: 对每一小组分别找出其中位数。 3. **递归找中位数**: 使用同样的方法对所有中位数组成的新数组继续寻找中位数,直到剩下少量元素可以直接处理为止。 4. **选定 Pivot**: 找到最终的中位数作为全局 pivot。 5. **分区与递归**: 利用此 pivot 进行分区,并针对较小部分或较大部分递归调用选择算法。 由于上述步骤中的 pivot 更加平衡,因此可以证明整个算法时间复杂度为 \(O(n)\)。 --- #### 实现代码示例 以下是 Python 中的一个简单实现,展示了如何利用 Median of Medians 来构建具有严格 \(O(n)\) 复杂度的选择算法: ```python def median_of_medians(arr, i): """ 寻找数组arr中的第i小元素 (0-based index) :param arr: 输入数组 :param i: 需要找到的位置索引 :return: 第i小的元素 """ def partition(x_arr, pivot_x_index): """根据指定的pivot重新排列数组""" pivot_value = x_arr[pivot_x_index] # 将pivot移到最后 x_arr[-1], x_arr[pivot_x_index] = x_arr[pivot_x_index], x_arr[-1] store_idx = 0 for j in range(len(x_arr)-1): if x_arr[j] < pivot_value: x_arr[store_idx], x_arr[j] = x_arr[j], x_arr[store_idx] store_idx += 1 # 把pivot放到正确的位置 x_arr[-1], x_arr[store_idx] = x_arr[store_idx], x_arr[-1] return store_idx def select_recursion(x_arr, stat_i): """递归执行选择算法""" if len(x_arr) == 1: assert(stat_i == 0) return x_arr[0] medians = [] sublists = [x_arr[k:k+5] for k in range(0, len(x_arr), 5)] for sublist in sublists: sorted_sublist = sorted(sublist) medians.append(sorted_sublist[len(sublist)//2]) pivot = select_recursion(medians, len(medians)//2) # 获取pivot在原数组中的index pivot_idx = None for idx, val in enumerate(x_arr): if val == pivot: pivot_idx = idx break pvt_idx = partition(x_arr, pivot_idx) if stat_i == pvt_idx: return x_arr[stat_i] elif stat_i < pvt_idx: return select_recursion(x_arr[:pvt_idx], stat_i) else: return select_recursion(x_arr[pvt_idx+1:], stat_i-pvt_idx-1) return select_recursion(arr.copy(), i) ``` --- #### 性能分析 对于任意输入序列,该算法始终能够在最坏情况下保持 \(O(n)\) 的时间复杂度。这是因为 Median of Medians 提供了一个良好的 pivot 选择机制,使每次分区后剩余待解决的部分不超过原始长度的约 \(\frac{7}{10}\)。由此可得总工作量构成几何级数收敛的形式,即总体复杂度为线性级别。 尽管如此,实际应用中可能更倾向于随机化版本的 Quickselect,因其常数因子较低且平均表现优异;但在需要绝对保障时,Median of Medians 显示出了无可比拟的优势。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值