题目要求
最近做了一道题,题目是这样的:
1 | 找到一个巨大数组的中位数。 |
2 | # demo:[1,100,2,5,12,44,88,77,54,932,61] |
解题方法
巨大的数组,排序肯定不是最优解了,解题思路可以借鉴快排算法那种分而治之的思想。
详情直接看代码实现吧。
1 | #!/usr/bin/env python |
2 | # -*- coding:utf-8 -*- |
3 | # author: toddler |
4 | |
5 | import random |
6 | import statistics |
7 | import time |
8 | import sys |
9 | sys.setrecursionlimit(1000000) |
10 | |
11 | |
12 | def find_mid(mid_index, __list): |
13 | """ |
14 | 寻找中位数算法 |
15 | :param mid_index: 中位数索引 |
16 | :param __list: 目标数组 |
17 | :return: 中位数数值 |
18 | """ |
19 | # 随机取一个数作为分割元素, 以分割元素为界限,将数组分割大小两部分 |
20 | random_num = random.choice(__list) |
21 | small_list = [i for i in __list if i < random_num] |
22 | # 若小数组的右端索引大于中位数索引, 则继续缩小小数组的区间长度, 这样可以直接舍弃比中位数大的元素, 减少计算量 |
23 | if len(small_list) > mid_index: |
24 | return find_mid(mid_index, small_list) |
25 | # 分割点左边的元素没有价值, 被舍弃, 相应的中位数索引左移对应长度, 保证相对原始数据索引长度不变 |
26 | mid_index -= len(small_list) |
27 | # 判断分割点有几个, 若分割点所占空间长度大于新的中位数索引, 则分割点就是中位数 |
28 | same_mid_num = __list.count(random_num) |
29 | if same_mid_num > mid_index: |
30 | return random_num |
31 | # 接下来向右计算, 所以切分点所占据的索引区间元素将不在计算, 大数组将舍弃这些值, 因此调整中位数的索引值 |
32 | mid_index -= same_mid_num |
33 | big_list = [i for i in __list if i > random_num] |
34 | return find_mid(mid_index, big_list) |
35 | |
36 | |
37 | def run(__list): |
38 | """ |
39 | 调度处理算法无关的业务逻辑 |
40 | :param __list: 目标数组 |
41 | :return: 中位数数值 |
42 | """ |
43 | list_length = len(__list) |
44 | if list_length != 0: |
45 | # 判断奇数个还是偶数个 |
46 | if list_length % 2: |
47 | mid_index = list_length // 2 |
48 | print("奇数个数字: {}".format(list_length)) |
49 | return find_mid(mid_index, __list) |
50 | else: |
51 | print("偶数个数字: {}".format(list_length)) |
52 | left_num = find_mid((list_length - 1) // 2, __list) |
53 | right_num = find_mid((list_length + 1) // 2, __list) |
54 | return (left_num + right_num) / 2 |
55 | else: |
56 | return "输入列表是否为空" |
57 | |
58 | |
59 | def test(test_data): |
60 | """ |
61 | 测试验证 |
62 | :param test_data: 待测数组 |
63 | :return: |
64 | """ |
65 | # print('原始数据: {}'.format(test_data)) |
66 | stand_s_time = time.clock() |
67 | expect_mid = statistics.median(test_data) |
68 | stand_e_time = time.clock() |
69 | print("statistics计算耗时: {}".format(stand_e_time - stand_s_time)) |
70 | start_time = time.clock() |
71 | actual_mid = run(test_data) |
72 | end_time = time.clock() |
73 | print("我的算法计算耗时: {}".format(end_time-start_time)) |
74 | print('期望结果: 中位数为{}'.format(expect_mid)) |
75 | print('实际结果: 中位数为{}'.format(actual_mid)) |
76 | assert expect_mid == actual_mid, '计算错误' |
77 | |
78 | |
79 | demo_list = [1, 100, 2, 5, 12, 44, 88, 77, 54, 932, 61] |
80 | print('样例测试=====>') |
81 | test(demo_list) |
82 | print('\r\n大数据量测试======>') |
83 | test([random.randint(0, int(1e6)) for _ in range(int(1e6))]) |
测试结果
测试环境:
硬件 | 指标 |
---|---|
CPU | Intel(R) Core(TM) i3-4370 CPU @ 3.80GHz |
MEM | 8G DDR3 |
算法表现,稳定性不是很好:
数据量 | 排序时间 |
---|---|
10 ^ 6 | 0.5 s |
10 ^ 7 | 5 ~ 6 s |
10 ^ 8 | 65 ~ 100 s |
总结分析
最差时间分析 | 平均时间复杂度 | 稳定度 | 空间复杂度 |
---|---|---|---|
O(n) | O(logn) | 不稳定 | O(logn) |