(转载请注明出处,http://www.cnblogs.com/fangpei/p/3538331.html )

以前写过的一篇,搬过来。

上算法课的时候听到老师讲这个问题,觉得还是蛮有意思的。已知数组A,找出A[m]...A[p]中的第k大值。

很容易想到快排和冒泡。

第一种方法:用快排的分治方法,是先任意找数组中的一个元素a(a用数组的第一个元素比较方便),然后进行一次划分,就是将数组中所有大于a的数都移到a的一边,所有小于等于a的数都移到A的另一边。然后选择在哪边继续进行划分,最后找到第k大的值。

第二种方法:用冒泡的方法,是每个元素挨着比,第一趟找出最大的数,第二趟找出第2大的数,一直到找到第k大的数结束。

其实第一种方法的平均复杂度能到O(n),但是它的复杂度依赖于划分元素,最坏的时间复杂度是O(n^2)。

      如果在第一种方法之上,加上一个筛选划分元素的过程,就能把最坏时间复杂度降到O(n)。筛选的过程就是把所有的数等分成很多小段,然后求所有小段的中间值。构成一个由所有中间值组成的段,然后再取中间值,作为划分元素。即中间值的中间值作为划分元素。取中间值可以先任选一种排序方法排序之后选择,因为每一小段的长度很短,不是影响复杂度的主要因素;取中间值的中间值,利用递归的方法调用自身即可。

这样就可以把最坏时间复杂度降到O(n)了,复杂度证明比较繁琐。

用C++实现了一下:

#include<iostream>
using namespace std; int r = ; //定义全局变量r, r个元素一段 void InSort( int A[], int m, int p ) //插入排序
{
int i;
for( i = m + ; i <= p; ++i ) {
int t;
t = A[i];
int j;
for( j = i - ; j >= m; --j ) {
if( t < A[j] )
A[j+] = A[j];
else
break;
}
A[j+] = t;
}
} void Swap( int &a, int &b ) //两数交换
{
int temp = ;
temp = a;
a = b;
b = temp;
} int Partition( int A[], int m, int p ) //一次划分函数
{
int i = m, j = p + ;
int x = A[m];
while( ) {
while( A[++i] > x );
while( A[--j] < x );
if( i >= j)
break;
Swap( A[i], A[j] );
}
A[m] = A[j];
A[j] = x;
return j;
} int Select( int A[], int m, int p, int k ) //返回一个i值,使得A[i]是A[m..p]中第k小元素
{
int n = , i = , j = ;
if( p - m + <= r ) {
InSort( A, m, p );
return m + k - ;
}
while( ) {
n = p - m + ;
for ( i = ; i <= int(n/r); ++i ) { //计算中间值
InSort( A, m + (i - ) * r, m + i * r - );
//将中间值收集到A[m..p]的前部
Swap( A[m+i-], A[m+(i-)*r+int(r/)] );
}
j = Select( A, m, m + int(n/r) -, int(int(n/r)/) + );
Swap( A[m], A[j] ); //产生划分元素
j = Partition( A, m, p );
if( j - m + == k)
return j;
else if( j - m + > k )
p = j - ;
else {
k = k - ( j - m + );
m = j + ;
}
}
} int main()
{
int A[] = { , , , , , , , , , , , , , , , , , , , , , , , };
int find_out = Select( A, , , );
int i;
for( i = ; i <= ; ++i )
cout << A[i] <<" ";
cout << endl;
cout << A[find_out] << endl;
return ;
}

另外:
1、上面说的都是在内存够用的前提下。
2、调这个程序的时候发现了一个问题:

以前我以为下面这样交换两个数比较好。
void Swap( int &a, int &b )
{
a = a ^ b;
b = a ^ b;
a = a ^ b;
}

才发现如果a和b表示同一个地址的时候,就是错的(不管是什么都变成0了)。

所以如果可能出现a、b是同一个地址上的数的时候,为了避免有隐藏的bug,还是下面这样保险。(虽然多了一个临时变量的空间)
void Swap( int &a, int &b )
{
int temp = ;
temp = a;
a = b;
b = temp;
}

找第k大数,最坏时间复杂度O(n)的更多相关文章

  1. 牛客网-3 网易编程题(1拓扑&2二叉树的公共最近祖先&3快排找第K大数)

    1. 小明陪小红去看钻石,他们从一堆钻石中随机抽取两颗并比较她们的重量.这些钻石的重量各不相同.在他们们比较了一段时间后,它们看中了两颗钻石g1和g2.现在请你根据之前比较的信息判断这两颗钻石的哪颗更 ...

  2. [剑指Offer]39-数组中出现次数超过一半的数字(快排延申,找第k大数同理)

    题目链接 https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&t ...

  3. 从一组数找第K大元素

    最近做面试题,经常与到一个问题,如何高效的从一组数中找到第K大的元素. 其实我们最容易想到的肯定是蛮力法. 1. 我们可以对这个乱序数组按照从大到小先行排序,然后取出前k大,总的时间复杂度为O(n*l ...

  4. 杨氏矩阵:查找x是否在矩阵中,第K大数

    参考:http://xudacheng06.blog.163.com/blog/static/4894143320127891610158/ 杨氏矩阵(Young Tableau)是一个很奇妙的数据结 ...

  5. 第k大数问题

    解法1: 我们可以对这个乱序数组按照从大到小先行排序,然后取出前k大,总的时间复杂度为O(n*logn + k). 解法2: 利用选择排序或交互排序,K次选择后即可得到第k大的数.总的时间复杂度为O( ...

  6. 算法打基础——顺序统计(找第k小数)

    这次主要是讲如何在线性时间下找n个元素的未排序序列中第k小的数.当然如果\(k=1 or k=n\),即找最大最小 数,线性时间内遍历即可完成,当拓展到一般,如中位数时,相关算法就值得研究了.这里还要 ...

  7. 第k大数(前k大数)

    题目:设计一组N个数,确定其中第k个最大值 1,普通方法(先排序,然后遍历,得到第k大的数)      注:如果是数组,直接arr[k],我们可以对这个乱序数组按照从大到小先行排序,然后取出前k大,总 ...

  8. POJ 2104 K-th Number(区间第k大数)(平方切割,归并树,划分树)

    题目链接: http://poj.org/problem? id=2104 解题思路: 由于查询的个数m非常大.朴素的求法无法在规定时间内求解. 因此应该选用合理的方式维护数据来做到高效地查询. 假设 ...

  9. BFPRT: O(n)最坏时间复杂度找第K大问题

    同时找到最大值与最小值 找到n个元素中的最大/小值,比较次数为n-1, 找到n个元素中的最大值和最小值,可以Two Pass,比较次数为2n-2 也可以One Pass,比较次数至多为\(\left ...

随机推荐

  1. Java基础知识强化之IO流笔记09:File类功能

    详见如下: Android(java)学习笔记87:File类使用

  2. Android(java)学习笔记223:上下文的区分

    1.两种上下文:  (1)Activity.this                               界面的上下文 (2)getApplicationContext()         整 ...

  3. CDOJ 92 – Journey 【LCA】

    [题意]给出一棵树,有n个点(2≤N≤105),每条边有权值,现在打算新修一条路径,给出新路径u的起点v,终点和权值,下面给出Q(1≤Q≤105)个询问(a,b)问如果都按照最短路径走,从a到b节省了 ...

  4. 如何参与一个GitHub开源项目

    Github作为开源项目的著名托管地,可谓无人不知,越来越多的个人和公司纷纷加入到Github的大家族里来,为开源尽一份绵薄之力.对于个人来讲,你把自己的项目托管到Github上并不表示你参与了Git ...

  5. codevs1044四子连棋(Dfs)

    /* 数据范围太小 暴力暴力 Dfs直接 终止条件嘛 就是4中目标棋局 挨着枚举一遍就好了 搜索的起点一定是空格 当然 空格周围有黑有白 黑先走或者白先走答案可能不一样 所以 维护一个b 表示这一步走 ...

  6. 【开源java游戏框架libgdx专题】-09-动画的使用

    1.Animation类介绍   Api定义:动画是由多个帧,在设定的时间间隔序列显示.比如,一个跑步的人一个动画可以通过运行时播放这些图像无限拍照他了. 功能用法:管理动画,设置随即播放模式和播放顺 ...

  7. Linq101-Miscellaneous

    using System; using System.Collections.Generic; using System.Linq; namespace Linq101 { class Miscell ...

  8. PHP 实现ajax的接收

    Ajax界面: 首先,理解本质,就是普通的一个提交在无刷新的情况下发出请求后得到响应,然后去针对你需要 的情况来做行为. <!DOCTYPE html> <html> < ...

  9. Webview Android与js交互

    Android 中可以通过webview来实现和js的交互,在程序中调用js代码,只需要将webview控件的支持js的属性设置为true Android(Java)与JavaScript(HTML) ...

  10. IOS开发常用的linux命令

    pwd 在Linux层次结构中,用户可以在被授权的任意目录下利用mkdir命令创建新目录,也可以利用cd命令从一个目录转换到另一个目录.然而,没有提示符来告知用户目前处于哪一个目录中.想要知道当前所处 ...