O(n)的时间复杂度求中位数

O(n)中位数问题是指:在O(n)的时间复杂度内找到一个无序序列的中位数。

在开始O(n)时间复杂度求中位数之前,先手写一下快速排序。

快速排序的实现

Reference:
快速排序|菜鸟教程
白话经典算法系列之六 快速排序 快速搞定

快速排序的原理

如果想正序排序一个序列:

  1. 从序列中找到一个 基准数
  2. 分区:将序列中比基准数大的数字都放到基准数的右边,将序列中比基准数小的数字都放在基准数的左边。
    • 此时,序列被分成了三部分: 左序列 + 基准数 + 右序列
    • 实现2的方法可以是 挖坑法
  3. 对左序列和有序列进行排序即可(递归),直到左/右序列长度为1

快速排序的复杂度

  1. 时间复杂度:

    • 最好:O(nlogn)
    • 最坏:O(n^2)
    • 平均:O(nlogn)
  2. 空间复杂度:O(nlogn)
  3. 稳定性:不稳定

快速排序的实现1

void quick_sort(int a[], int left, int right){if (left >= right){return;}int l = left, r = right;int x = a[left]; // 选取当前序列最左边的数字为基准数while (l < r){while (l < r && a[r] >= x){r --;}if (l < r){a[l] = a[r];l ++;}while (l < r && a[l] < x){l ++;}if (l < r){a[r] = a[l];r ++;}}a[l] = x;quick_sort(a, left, l - 1);quick_sort(a, l + 1, right);
}
  • 每一次快排都会确定一个位置,这个位置是l,大小是基准数
  • 如果我们想每一次都知道 快速排序确定的位置 ,那么可以写一个partition函数。(事实上,这是在为O(n)解决中位数问题做铺垫。)

快速排序的实现2——partition函数

int partition(int a[], int l, int r){int x = a[l];while (l < r){while (l < r && a[r] >= x){r --;}if (l < r){ a[l] = a[r];l ++;}while (l < r && a[l] < x){l ++;}if (l < r){a[r] = a[l];r --;}}a[l] = x;return l;
}void Q_sort(int a[], int l, int r){if (l < r){int index = partition(a, l, r);Q_sort(a, l, index-1);Q_sort(a, index+1, r);}
}
  • partiton()负责将获得每一次进行步骤2:分区得到的基准数在最终递增序列中的 位置

  • Q_sort()中的index就是该位置。根据该位置将序列分为左右序列。

有了partition函数,就可以实现O(n)时间复杂度找中位数的工作了。

基于partition函数的O(n)中位数

显然,如果没有O(n)的限制,那么一个直白的想法是:将无序序列排序,然后输出序列的 n/2个位置 的元素。

  • n是序列的长度。
  • 其实,对于中位数应该分情况讨论:
    • n 是奇数时,中位数是a[n/2]
    • n 是偶数时,中位数是(a[n/2] + a[n/2 - 1]) / 2.0

上述算法的时间复杂度是O(nlogn)

考虑到,对于partition函数,每一个可以确定一个位置! 那么,假设这个位置是index,那么对于中位数的位置pos:

  • 如果index = pos,显然,找到了中位数a[index]
  • 如果index > pos,显然,中位数位于区间[l, index-1]
    • 此时,只需对区间[l, index-1]再次进行partition操作即可。
  • 如果index < pos,显然,中位数位于区间[index+1, r]
    • 同上。

根据上述思想,编写函数getMidNumber():

int getMidNumber(int a[], int l, int r, int pos){while (true){int index = partition(a, l, r); // 获得基准数的位置if (index == pos){return a[index];}else if (index > pos){ // 只需要在[l, index-1]区间内找pos位置即可r = index - 1;}else { // 只需要在[index, r]区间内找pos位置即可l = index + 1;}}return -1; // 一般程序不会到这里
}
  • 其中,pos的含义是中位数的位置:

    • 当序列长度为奇数时,pos = n/2
    • 当序列长度为偶数时,pos = n/2 或 n/2 - 1

比如,可以编写如下代码测试:

int main(){int a[] = {10, 8, 3, 5, 6, 2, 1, 7, 9, 4};int aLen = sizeof(a) / sizeof(int);if (aLen&1){cout << "Mid= " << getMidNumber(a, 0, aLen-1, aLen/2) << endl;}else{cout << "Mid= " << (getMidNumber(a, 0, aLen-1, aLen/2) + getMidNumber(a, 0, aLen-1, aLen/2-1)) / 2.0 << endl;}return 0;
}
  • aLen是序列a的长度。

时间复杂度分析

最坏:O(n^2)

  • 假设一种极端情况,每一次选取的基准数都是序列中最小的那个数字,因此partition函数会依次返回0,1,2...n/2,每一次partition函数都需要O(n)的时间复杂度。因此,最坏的时间复杂度为O(n^2)

最好: O(n)

  • 假设一种完美情况,第一次得到的基准数就是中位数,那么只需要执行一次partition函数,因此时间复杂度是O(n)

平均: O(n)
数学不好,证明不会。据 他们 说该算法的 期望 时间复杂度是O(n)
这好像是涉及 主定理MasterTheorem。搜了好多网页博客也没看懂。

Reference:
主定理 Master Theorem

拓展:找序列中第K大的数字

其实找中位数就是找序列中第n/2大的数字。

因此找需要调用getMidNumber(a, 0, aLen-1, k)即可找到序列中第k大的数字了。

大佬的一种更简洁的写法

O(n)求中位数和第k大数


2020.9.26 14:41 周六

O(n)的时间复杂度求中位数相关推荐

  1. 堆实战(动态数据流求top k大元素,动态数据流求中位数)

    动态数据集合中求top k大元素 第1大,第2大 ...第k大 k是这群体里最小的所以要建立个小顶堆 只需要维护一个大小为k的小顶堆 即可当来的元素(newCome)> 堆顶元素(smallTo ...

  2. 【两个有序数组求中位数】

    /* 两个有序数组求中位数问题; 这个题有很多方法: 方法一:排序,找到中位数: 方法二:归并排序的思想 方法三:转换成求第k小值   */ /* 思路:使用二分查找,时间复杂度为log(m+n). ...

  3. Leetcode算法题:两个有序数组求中位数

    Leetcode算法题:两个有序数组求中位数 要求时间复杂度为O(log(m+n)) 思路: 暴力解决:合并数组并排序,简单且一定能实现,时间复杂度O(m+n) 由于两个数组已经排好序,可一边排序一边 ...

  4. 线性O(N)时间复杂度求素数 , 筛法

    线性O(N)时间复杂度求素数 , 筛法 1 /* 2 线性时间求出1-N 的素数 , 时间复杂度为O( N) : 3 一个合数可以表示成若干个素数的积 4 比如说 i = 6 =2 * 3 , A = ...

  5. 算法练习day6——190323(求中位数、堆排序、稳定性)

    1.求中位数 有一个流,不断输出数,然后会不停地询问已输出的那些书的中位数. 解决方法:用一个大根堆,一个小根堆存那些已输出的数. 大根堆中存储较小的N/2个数: 小根堆中存储较大的N/2个数: 这样 ...

  6. 求中位数_图解面试题:如何分析中位数?

    学校每次考试完,都会有一个成绩表.例如,表中第1行表示编号为1的用户选择了C++岗位,该科目考了11001分. 问题:写一个sql语句查询每个岗位的中位数位置的范围,并且按岗位升序排序,结果如下: 解 ...

  7. 线性时间复杂度求数组中第K大数

    求数组中第K大的数可以基于快排序思想,步骤如下: 1.随机选择一个支点 2.将比支点大的数,放到数组左边:将比支点小的数放到数组右边:将支点放到中间(属于左部分) 3.设左部分的长度为L, 当K &l ...

  8. 给定一个整数序列,求中位数

    问题描述: 给定一个整数序列,求中位数.如果序列个数为奇数,中位数为升序的中间位置,如果是偶数,这位升序的中间两个数的平均值. 输入: 输入包含多组测试数据,每一组第一行为n(n<104)表示这 ...

  9. hive、impala 求中位数

    hive求中位数: (0.5参数可调) select percentile(cast(p as int),0.5) from student2; impala求中位数: select APPX_MED ...

最新文章

  1. g++使用C++11编译源文件
  2. 关于C#写的记事本中一个问题
  3. python中定义的函数不掉用不会执行_如果出现异常/错误,如何不在python中停止执行其他函数...
  4. jhipster 配置 mysql_java – 将jhipster后端和前端分成两个项目?
  5. 错误提示之:已超过传入消息(65536)的最大消息大小配额。若要增加配额,请使用相应绑定元素上的 MaxReceivedMessageSize 属性...
  6. Qt元对象QMetaObject的indexOfSlot等函数获取类方法注意问题
  7. LINUX前期知识回顾
  8. Qt工作笔记-线程池作用之一:限制系统中执行线程的数量
  9. linux shell grep -v grep|awk ‘{print $1}‘ 是什么意思
  10. VC++6.0安装完成后MSDEV.EXE出错的解决办法
  11. CSDN开发者云平台体验
  12. 武汉理工大学刷课,刷在线作业程序,做作业脚本
  13. 使用端口扫描工具消除端口安全威胁
  14. 通过有限差分和matlab矩阵运算直接求解一维薛定谔方程,通过有限差分和MATLAB矩阵运算直接求解一维薛定谔方程...
  15. latex设置页面大小边距行距等
  16. python 处理 Excel 表格
  17. 关于程序员的职业规划分析
  18. 福利分享:1024程序员节,给大家推荐一个极简win10
  19. 计算机二级c语言预测,计算机二级C语言考前预测上机试题及解析
  20. latex 插入Logo

热门文章

  1. 计算机 计算能力测试题,高中数学计算能力训练题.doc
  2. 学习笔记四:word2vec和fasttext
  3. 最佳情侣身高差 (10 分)
  4. Android 拨打电话各安卓版本适用
  5. 从新建工程开始使用C++开发单片机(以STM32为例):一、项目介绍
  6. form-login属性详解
  7. Open×××优化之-巨型帧
  8. Python+Vue计算机毕业设计停车场管理系统8f46a(源码+程序+LW+部署)
  9. revit中在三维视图下显示房间文字和“房间集成”
  10. DeepLearning: 归纳几种常用的激活函数(activation)