在许多算法与数据分析任务中,我们不仅需要排序整个数组,还需要找出某个特定顺序统计量,例如:
• 一个数组中第 K 大的元素;
• 所有元素的中位数(K = n/2);
• 某个分位点(25%、75%等)…
最直接的办法是先对数组进行排序,然后取第 K 个位置的元素。这种方法的时间复杂度为
但我们真正关心的,只是一个位置的元素 —— 我们是否有更快的方法?
快排中的“轴枢”启发
我们在快速排序中,每次选一个枢轴 pivot,通过划分操作把数组分成两部分:
• 左边元素 <= pivot;
• 右边元素 >= pivot。
最终 pivot 会被放置固定位置上。
可见:
若 pivot 恰好是我们要找的第 K 大元素,就不必继续递归。
实现一个 quickselect(arr, k) 函数,返回第 K 大的元素
(也就是增序列下标N-K位置的数)
import random
def partition(arr, low, high):
"""
对arr序列从low到high下标,找到一个枢轴,并返回其下标。(在arr中,比枢轴小的数都在idx左边,大的在右边)
"""
def quickselect(arr, k):
"""
快速选择第K大元素(转换为第n-k小)
:param arr: 输入数组
:param k: 要找的第K大元素
:return: 该元素值
"""
n = len(arr)
target = n - k # 第k大转为第n-k小(序列下标)
low, high = 0, n - 1
while low <= high:
idx = partition(arr, low, high)
if idx == target:
return arr[idx]
elif idx < target:
low = idx + 1
else:
high = idx - 1
if __name__ == "__main__":
#测试1:随机数组
random.seed(42) # 设置随机种子,确保结果可重现
arr1 = [random.randint(1, 100) for _ in range(10)]
k1 = 3
result1 = quickselect(arr1.copy(), k1) # 使用副本避免修改原数组
sorted_arr1 = sorted(arr1)
expected1 = sorted_arr1[-k1]
print(f"测试1 - 随机数组:")
print(f"原数组: {arr1}")
print(f"第{k1}大元素: {result1}, 预期值: {expected1}")
print(f"测试{'通过' if result1 == expected1 else '失败'}\n")
#测试2:包含重复元素的数组
arr2 = [5, 3, 8, 8, 1, 5, 9, 3, 5]
k2 = 2
result2 = quickselect(arr2.copy(), k2)
sorted_arr2 = sorted(arr2)
expected2 = sorted_arr2[-k2]
print(f"测试2 - 包含重复元素:")
print(f"原数组: {arr2}")
print(f"第{k2}大元素: {result2}, 预期值: {expected2}")
print(f"测试{'通过' if result2 == expected2 else '失败'}\n")
#测试3:边界条件 - k=1(最大元素)
arr3 = [10, 20, 30, 40, 50]
k3 = 1
result3 = quickselect(arr3.copy(), k3)
sorted_arr3 = sorted(arr3)
expected3 = sorted_arr3[-k3]
print(f"测试3 - 最大元素:")
print(f"原数组: {arr3}")
print(f"第{k3}大元素: {result3}, 预期值: {expected3}")
print(f"测试{'通过' if result3 == expected3 else '失败'}\n")
快速选择的平均复杂度为
BFPRT算法和二分查找中对“矩阵”进行分治的做法很像,也是通过矩阵的中心数,来排除一部分数据。
我们通过以下几个步骤确保选出的“轴枢”可以高概率地“削减”足够多的元素,从而保证递归过程每次至少减少 30% 左右的输入规模:
与AI教师讨论每一步在做的事和最终想要实现的效果。可以联想你之前学习到的二分查找中的矩阵分治。
在理解了“中位数的中位数”算法的流程之后,我们接下来要正式分析它的时间复杂度,并证明该算法的整体运行时间为
根据 BFPRT 算法的执行过程,依次与AI教师讨论每步的时间复杂度:
从而得到最终的递推式。
这里的证明并不复杂,使用高中学习的数学归纳法即可。 提示:先假设满足T(n)<=cn;然后证明递推式<=d*cn<=cn,其中d小于1。
与AI教师探讨证明流程。