得到第K个大的数算法研究

本文为原创,如需转载,请注明作者和出处,谢谢!

   
第一种算法是最容易想到的,就是利用快速排序的思想,将一个数组分成以某一个数X为轴,左边的所有的数都比X小,而右边的数都比X大。但我快速排序不同的是,在这个算法中只考虑X的一边,而不是两边都考虑。

   如果X的位置是i,那么要得到第k个数,如果k<=i, 那么这个数一定在i的左边,否则在i的右边。

源码如下:

#include <stdio.h>
#include 
<stdlib.h>
int new_random(int min, int max)
{
    
return (min + (int)(((float)rand()/RAND_MAX)*(max - min)));
}
void swap(int *a, int *b)
{
    
int c = *a;
    
*= *b;
    
*= c;
}

int partition(int A[], int p, int r)
{
    
int i = p - 1, j;
    
for(j = p; j < r; j++)
    {
        
if(A[j] <= A[r])
        {
            i
++;
            swap(
&A[i], &A[j]);
        }
    }
    swap(
&A[i + 1], &A[r]);
    
return i + 1;
}

int randomize_partition(int A[], int p, int r)
{
    
int i = new_random(p, r);
    swap(
&A[i], &A[r]);
    
return partition(A, p, r);
}

//第一种算法
int randomized_select(int data[], int p, int r, int k)
{
    
if(k > (r - p + 1)) return 0;
    
if(p == r) return data[p];
    
int i = randomize_partition(data, p, r);
    
//int i = partition(data, p, r); //不好使,死循环, 必须用随机数,在第二步时总是在最大数处划分

    
int count = i - p + 1;
    
if(k <= count)
        
return randomized_select(data, p, i, k);
    
else
        
return randomized_select(data, i + 1, r, k - count);
}


    另外一种对这种算法做了一下改进,即将数组每5个数一组进行排序,然后取得它的中位数,再对这些中位数进行排序。然后先出的轴X最比较好的,因此X的左边和右边的数总是很平均,所以平均查找速度更快。算法如下:

void quicksort(int data[], int b, int e)
{
    
if(b < e)
    {
        
int k = partition(data, b, e);
        quicksort(data, b, k 
- 1);
        quicksort(data, k 
+ 1, e);
    }
}

int partition1(int A[], int p, int r, int x)
{
    
int i = p - 1, j;
    
for(j = p; j <= r; j++)
    {
        
if(A[j] <= x)
        {
            i
++;
            swap(
&A[i], &A[j]);
        }
    }
    A[i 
+ 1= x;
    
return i + 1;
}
//第二种算法
int select_new(int data[], int p, int r, int k)
{
    
if(r - p < 75)
    {
        quicksort(data, p, r);
        
return data[p + k - 1];
    }   
    
int i;
    
for(i = 0; i <= (r - p - 4/ 5; i++)
    {
        quicksort(data, p 
+ 5 * i, p + 5 * i + 4);
        swap(
&data[p + 5 * i + 2], &data[p + i]);
    }
    
int x = select_new(data, p, p + (r - p - 4/ 5, (r - p - 4)/10); // 得到更好的轴X
    i = partition1(data, p, r, x);
    
int count = i - p + 1
    
if(k <= count)
        
return select_new(data, p, i, k);
    
else
        
return select_new(data, i + 1, r, k - count);
}

int main()
{
    
int data[] = {3173481167812-1100};
    printf(
"%d\n", randomized_select(data, 092));
    
int data1[] = {3173481167812-1100};
    printf(
"%d\n", select_new(data1, 092));
   
     
return 0;
}

posted on 2008-05-12 21:05 银河使者 阅读(1129) 评论(18)  编辑 收藏 所属分类: algorithm 原创

评论

#1楼  2008-05-12 23:16 Angel Lucifer      

我想了解一下楼主的方法跟三元素中值划分的快速选择算法到底哪个性能更佳呢?

它们的时间复杂度应该是一样的,只是不知哪个更佳?

楼主采用的是随机选择和5个元素分组排序找中值的方法,我还没怎么明白它更好在哪个地方,也没测试过其具体性能如何。

还望楼主指教。   回复  引用  查看    

#2楼  2008-05-13 08:45 小猴子      

兄弟我没仔细研究,不过怎么觉得你的这个算法实现的前提就不对。
在这个算法中只考虑X的一边,而不是两边都考虑
这点还请LZ好好思考下   回复  引用  查看    

#3楼  2008-05-13 10:05 装配脑袋      

五分化中项的中项理论上有O(N)上限,但是实际比较费时。伪随机算法要用到除法,同样可能不是那么快。所以,一般只要挑序列两边和中间这三个数中第二大的那个数来划分就行了,很快速,而且极难出现最差情形。   回复  引用  查看    

#4楼  2008-05-13 13:39 疯狂的程序员 [未注册用户]

这里有一个参考

类型4.一般性选择问题
输入:数组L, L的长度n, 正整数k, 1≤ k≤ n.
输出:第k小的数
通常算法
1.排序
2.找第k小的数
时间复杂性:O(nlogn)

算法4 Select(S,k) 改进的算法
1.将S划分成5个一组, 共nM= [n/5] 个组
2.每组找中位数,nM个中位数放到集合M
3.m*←Select(M, |M|/2 ) 将S中的数划分成A,B,C,D四个集合
4.把A和D中的每个元素与m*比较,小的构成S1, 大的构成S2
5.S1←S1∪C; S2←S2∪B
6.if k =|S1|+1 then 输出m*
7.else if k≤|S1|
8. then Select(S1,k)
9. else Select(S2,k-|S1|-1)

复杂性估计 更详细的请参考王晓东的《计算机算法设计与分析》(第2版) 电子工业出版社 P24-26

复杂性:W(n)=O(n)
行2: O(n)
行3: W(n/5)
行4: O(n)
行8-9: O(n)

来自:http://www.kuqin.com/algorithm/20080229/4076.html   回复  引用    

#5楼  2008-05-13 16:53 Angel Lucifer      

@装配脑袋
我也觉得三元素中值划分的方案更加可行。呵呵。
看重的就是它比较快,而且极难出现最差情况。

对于小数组,即元素数少于20左右的数组,还应当采用插入排序来进一步优化。   回复  引用  查看    

#6楼  2008-05-13 17:18 装配脑袋      

如果用优先队列,可以实现k-选择问题O(N*Log(k))或O(N+k*Log(k))的解法,这用于k较小时比较有优势。比如求第10大元素   回复  引用  查看    

#7楼 [楼主] 2008-05-14 14:53 银河使者      

to 小猴子

这不是折半查找算法,是寻找第K个数。仔细看看算法程序。
还有就是这个算法并不是只考虑X的一边,是两边都考虑了,只是X的左边或右边的所有数都不可能,所有就不考虑了。   回复  引用  查看    

#8楼 [楼主] 2008-05-15 08:55 银河使者      

首先说明,初始数组是无序的,无法直接用索引,是找第k个大的数,不是找第K个数。只有将数组非序,才能确定第K个大的数。   回复  引用  查看    

#9楼 [楼主] 2008-05-15 08:57 银河使者      

我的第一个思想是使用快速排序的思想,不是进行快速排序,这种方法要比进行排序后再查找更快。   回复  引用  查看    

#10楼 [楼主] 2008-05-15 14:07 银河使者      

看看这句话:将一个数组分成以某一个数X为轴,左边的所有的数都比X小,而右边的数都比X大。

这个理论没错,我们可以举个具体的例子,如X = 10,它的位置是i = 6,也就是说i < 6的所有数都比X小(但i < 6的一组数并不是有序的),而i > 6的所有数都比X大,现在假设要找第4个数,也就是说k = 4,这样就k < i了。假设索引从0开始,那么在X左边有i = 0 .. 5,共6个数要比X小,虽然这6个数并未排序,但是第4个大的数一定在这6个数中,因为这6个数就是这个序列的前6个数。你说对吗?

当k > i时,同理,understand?   回复  引用  查看    

#11楼 [楼主] 2008-05-15 14:13 银河使者      

我用的递归算法,得按递归的方式思考   回复  引用  查看    

#12楼 [楼主] 2008-05-15 16:28 银河使者      

你理解错了,我的数组没有前提,是任意一个数组,所谓以X为轴,是在递归算法中处理的。下面的代码已经很清楚了

int randomized_select(int data[], int p, int r, int k)
{
if(k > (r - p + 1)) return 0;
if(p == r) return data[p];
int i = randomize_partition(data, p, r);

int count = i - p + 1;
if(k <= count)
return randomized_select(data, p, i, k);
else
return randomized_select(data, i + 1, r, k - count);
}

其中data表示数组,p和r分别表示data数组或data子数组的上界和下界,k表示第k个大的数。你可以运行一下程序,看看运行结果就知道了。这都是在机器上调试通过了。不会有问题。   回复  引用  查看    

#13楼 [楼主] 2008-05-15 18:20 银河使者      

哪位有更好的算法,可以跟贴,不要在这里空谈理论。如果谁有比这个算法更高效的算法,不防提供一个,让大家也开开眼。

我可并没说这个算法很高效,只是可以达到目的而已。哈哈。-^-
  回复  引用  查看    

#14楼 [楼主] 2008-05-23 17:33 银河使者      

to 小猴子

仔细看一看,返回的是值,不是索引,第二小的当然是1了, 最小的是-1,^-^   回复  引用  查看    

#15楼 [楼主] 2008-05-24 09:16 银河使者      

哈哈,只是转来转去,有的是别人转的,其他的blog现在不用了,目前只有两个blog;www.blogjava.net/nokiaguy和www.cnblogs.com/nokiaguy

这个算法先讨论到这吧,它是我很多年前写的一个,现在有些细节的地方都记不清了。放到这个blog上只是做个备份。当初加上详细的注释就好了。有空再仔细研究一下。这个算法使用了递归,效率不高,得研究一下非递归的。效率能高点。 希望多交流。   回复  引用  查看    

#16楼 [楼主] 2008-05-24 09:18 银河使者      

谁有更高效的算法,也可以跟贴。^-^   回复  引用  查看    

#17楼  2008-05-24 09:35 hxmhxl [未注册用户]

调试的时候K=1时是-1,那就证明你是选最小的数了。文字表达的问题,标题改改就行了。   回复  引用    

#18楼  2008-06-10 09:42 小猴子      

不好意思!我把自己的回帖删除了。感觉自己在拍砖。   回复  引用  查看    


标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-05-22 17:08 编辑过
 
另存  打印
 


<2008年5月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

导航

统计

公告

请使用站内搜索:
(不要按回车,请直接点击查询按钮)



我的其它Blog:

nokiaguy.blogjava.net

与我联系

常用链接

留言簿(3)

我参与的团队

我的标签

随笔分类(105)

随笔档案(51)

相册

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜

60天内阅读排行