使用Hoare算法计算select[k]

问题很简单, n个元素list[0], list[1]... list[n-1], 求其第k小(或大)的元素 (注: 为了简单, 程序中是按照0-index. 也就是说第0小的是最小的,以此类推)

最简单的想法, 给这些元素排个序, 自然就出来了. 当然这样弄效率不是很高, 因为计算了许多额外的信息.

使用Hoare算法, 可以比较好的解决这个问题.

基本思路和快排类似. 分为以下几个步骤:

1. 分块. 目的是把队列中所有的元素, 小于某个值(p) 的元素放到 p 左面, 大于p的放到 p 右面, 最后返回p所在的索引. 比如, 队列为

1 3 2 4 6 7     选择的元素为a[1] = 3. 那么,分块后,队列为 1 2 3 6 4 7 返回3的位置2. (这个很熟悉吧?和快排的基本一样)

    public static <E extends Comparable<? super E>> int partition(List<E> list, int left, int right, int pivotIndex) {

       debug ( "partion :" + pivotIndex + " left:" + left + " right:" + right);

       debug (list);

       E pivotValue = list.get(pivotIndex);

       swap (list, pivotIndex, right);

       int storeIndex = left;

       for ( int i=left; i<=right-1; i++) {

           if (list.get(i).compareTo(pivotValue) < 0) {

               swap (list, storeIndex, i);

             storeIndex = storeIndex + 1;

        }

       }

       swap (list, right, storeIndex);

       debug (list);

       //System.out.println("partion finished:" + storeIndex);    

       return storeIndex;

    }

2. 分块后, 就可以进行判断.

设经过第1步分块后, 得到的索引为index.

如果, index == k , 那么问题解决;

 

如 果, index > k , 则说明 待求的第 k 个元素还在 队列的左边. 则对队列index左边的元素进行处理. 如下例, P 是 选择的元素, a0-a4 都比 P 小, b0-b4都比P大. 此时P的位置是 5. 若要求的第 k 小, 并且 k < 5 , 那么显然第k小的元素是坐落在a0~a4中. 在程序中, 是队列left到pivotNewIndex - 1 的位置;

 

a0 a1 a2 a3 a4 P b0 b1 b2 b3 b4


 

如果, index < k , 则说明 待求的第 k 个元素还在 队列的右边. 则对队列index右边的元素进行分块,重复这些步骤(上图中b0~b4的部分).

 

 

程序如下:

 

    public static <E extends Comparable<? super E>> E select(List<E> list, int k, int left, int right) {

       //随便选择一个作为P. 这里用的中点

       int pivotIndex = (left + right) / 2;

       int pivotNewIndex = partition (list, left, right, pivotIndex);

       if (k == pivotNewIndex) {

           return list.get(k);

       }

       if (k < pivotNewIndex) {

           debug ( "less " + k + " " + pivotNewIndex);

           return select (list, k, left, pivotNewIndex - 1);

       } else {

           debug ( "great " + k + " " + pivotNewIndex);

           return select (list, k, pivotNewIndex + 1 , right);

       }

    } //end func

 

这里用到了递归. 为了简化, 可以消除递归:

    public static <E extends Comparable<? super E>> E select(List<E> list, int k, int left, int right) {

       //select a pivot value list[pivotIndex]

       while ( true ) {

           int pivotIndex = (left + right) / 2;

           int pivotNewIndex = partition (list, left, right, pivotIndex);

           if (k == pivotNewIndex) {

              return list.get(k);

           }

           if (k < pivotNewIndex) {

              right = pivotNewIndex - 1;

           } else {

              debug ( "great " + k + " " + pivotNewIndex);

              left = pivotNewIndex + 1;

           }

       }

    } //end func

 

 

 

最后写个包装的函数, 该函数返回list中第k小的元素. 要求List中的元素实现Comparable接口.

 

 

    public static <E extends Comparable<? super E>> E select(List<E> list , int k) {

       if (list == null && list .size() == 0) {

           return null ;

       } //end if

       return select (list, k, 0, list.size() - 1);

    } //end func

 

 

 

性能测试:

如果元素数目少, 基本上体现不出性能的好处. 如果数目多的话, 就可以看出来.

我测试 1000000 个 Interger 的 List, 和使用Collections.sort相比,

Hoare算法: 172ms

Collections.sort: 1390ms

差距还是挺大的.

 

参考:

http://en.wikipedia.org/wiki/Selection_algorithm

 

http://hi.baidu.com/lff0305/blog/item/c1b6a07e5c141c3f0dd7da25.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值