查看: 264|回复: 0

Python数组中位数算法:排序法与随机快速选择实现

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
在Python算法题和日常脚本开发中,求数组中位数是一个高频需求。中位数的定义取决于数组长度 n:n 为奇数时,取排序后中间位置的元素;n 为偶数时,取排序后中间两个元素的平均值。例如 arr = [12, 3, 5, 7, 4, 19, 26],排序后为 [3, 4, 5, 7, 12, 19, 26],元素个数为奇数,中位数为第 4 个元素 7。再如 arr = [12, 3, 5, 7, 4, 26],排序后为 [3, 4, 5, 7, 12, 26],元素个数为偶数,中位数为 (5 + 7) / 2 = 6。

下面按实现复杂度从低到高,整理三种常见思路:排序法、随机快速选择,以及基于顺序统计量的最坏情况线性时间方法。前两种给出可直接运行的 Python 代码。

一、朴素排序法
基本思路是先对数组排序,然后判断长度奇偶:奇数返回中间元素,偶数返回中间两个元素的平均值。代码实现如下:
  1. def findMedian(arr):
  2.     n = len(arr)
  3.     # First we sort the array
  4.     arr.sort()
  5.     # check for even case
  6.     if n % 2 != 0:
  7.         return arr[n // 2]
  8.     return (arr[(n - 1) // 2] + arr[n // 2]) / 2.0
  9. if __name__ == '__main__':
  10.     arr = [1, 3, 4, 2, 7, 5, 8, 6]
  11.     ans = findMedian(arr)
  12.     print(ans)
复制代码

输出:
4.5

该示例中数组排序后为 [1, 2, 3, 4, 5, 6, 7, 8],中间两个元素是 4 和 5,因此返回 4.5。时间复杂度为 O(n log n),主要消耗在排序;辅助空间为 O(1)。注意 arr.sort() 是原地排序,会修改传入的列表,如果调用方不希望原数组被改变,需要先复制一份再传入。

二、随机快速选择法
随机快速选择的思路与快速排序分区类似:随机选择基准元素,将较小元素放到左侧、较大元素放到右侧。如果基准最终落在中间索引,就找到了中位数;否则递归处理左半部分或右半部分。偶数长度数组需要找到中间两个元素,再计算平均值。实现如下:
  1. import random
  2. def swap(arr, i, j):
  3.     arr[i], arr[j] = arr[j], arr[i]
  4. def partition(arr, l, r):
  5.     lst = arr[r]
  6.     i = l
  7.     j = l
  8.     while j < r:
  9.         if arr[j] < lst:
  10.             swap(arr, i, j)
  11.             i += 1
  12.         j += 1
  13.     swap(arr, i, r)
  14.     return i
  15. def randomPartition(arr, l, r):
  16.     n = r - l + 1
  17.     pivot = random.randint(0, n - 1)
  18.     swap(arr, l + pivot, r)
  19.     return partition(arr, l, r)
  20. def medianUtil(arr, l, r, k, a, b):
  21.     if l <= r:
  22.         partitionIndex = randomPartition(arr, l, r)
  23.         # find the median of odd number element in arr[]
  24.         if partitionIndex == k:
  25.             b[0] = arr[partitionIndex]
  26.             if a[0] != -1:
  27.                 return
  28.         elif partitionIndex == k - 1:  # a & b as middle element of arr[]
  29.             a[0] = arr[partitionIndex]
  30.             if b[0] != -1:
  31.                 return
  32.         # index in first half of the arr[]
  33.         if partitionIndex >= k:
  34.             medianUtil(arr, l, partitionIndex - 1, k, a, b)
  35.         # find the index in second half of the arr[]
  36.         else:
  37.             medianUtil(arr, partitionIndex + 1, r, k, a, b)
  38. def findMedian(arr):
  39.     a = [-1]
  40.     b = [-1]
  41.     n = len(arr)
  42.     if n % 2 == 1:
  43.         medianUtil(arr, 0, n - 1, n // 2, a, b)
  44.         return b[0]
  45.     else:
  46.         medianUtil(arr, 0, n - 1, n // 2, a, b)
  47.         return (a[0] + b[0]) / 2.0
  48. if __name__ == '__main__':
  49.     arr = [12, 3, 6, 7, 4, 19]
  50.     print(findMedian(arr))
复制代码

输出:
6.5

该示例排序后为 [3, 4, 6, 7, 12, 19],中间两个元素是 6 和 7,因此结果为 6.5。这里用 a[0] 和 b[0] 分别记录中间两个位置的值,初始值为 -1 作为未找到标记。时间复杂度方面:最佳情况为 O(1),平均情况为 O(n),最坏情况为 O(n^2);辅助空间为 O(n)。随机化能降低遇到极端分区概率,但并不能从理论上完全消除最坏情况。

三、最坏情况线性时间方法:顺序统计量
这种方法的思路与 quickSelect() 类似,但通过选择能够平衡分割数组的枢轴点,避免一侧元素极少、另一侧元素过多,从而在理论上实现最坏情况线性时间复杂度。数组被平衡分割后,再按照 quickSelect() 的步骤决定从枢轴点左侧还是右侧继续选择。相关实现细节可参考“无序数组中第 K 个最小/最大元素 | 最坏情况下的线性时间复杂度”。原文也指出,虽然该方法理论上表现不错,但前面的随机快速选择方法在实践中通常效果更好。

总结
如果追求代码简洁、数据规模不大,直接排序后取中位数最直观;如果希望平均情况下更快,并且允许修改数组,可以使用随机快速选择;如果关注理论上的最坏情况线性时间,可以进一步研究顺序统计量方法。实际脚本开发中,随机快速选择在平均性能与实现复杂度之间较为均衡,但需要注意它依赖随机分区,最坏情况仍为 O(n^2)。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-10 15:21 , Processed in 0.034818 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部