“选择算法”

翻译自维基百科:http://en.wikipedia.org/wiki/Selection_algorithm

        在计算机科学里,选择算法(selection algorithm)是一种用于在一个列表中查找第K小的数的算法(这个数也被称之为第K个顺序统计量)。这类算法包括查找最小值、最大值和中值三类。这里有一些最坏时间复杂度为O(n)的选择算法。选择问题是更为复杂的问题,例如最近邻问题和最短路径问题的子问题。

        术语“选择”也被用在计算机科学界得其它上下文中。比如,遗传算法中的选择,是指从一代个体中选取的用于繁殖下一代个体的基因。本篇文章仅涉及如何确定顺序统计量的问题。

1、通过排序实现选择。

        选择问题可以通过对列表中的数据进行排序,然后提取需要的数值来简化为一个排序问题。当需要对列表中的数据进行排序时,该方法十分有效。在这种情况下,仅需要一个初始的、计算时间昂贵的排序算法和一些列后续的提取操作就可以完成目标。总的来说,这种方法的时间复杂度为O(n log n),这里的n指代列表的长度。

2、非线性的通用选择算法

   利用与求解最大、最小值相似的思想,我们可以实现一个简单,但是费时的用于在一个列表中查找第K小或第K大的数。这种方法的时间复杂度为O(kn),当K的值较小时很有效。为了实现该算法,我们仅需要简单的在列表中找出最小或最大值,并将它移动到列表的开头,知道我们得到想要的第K个数。这可以看做是一种选择排序,下面给出选择第K小的数的算法:

function select(list[1..n], k)
     for i from 1 to k
         minIndex = i
         minValue = list[i]
         for j from i+1 to n
             if list[j] < minValue
                 minIndex = j
                 minValue = list[j]
         swap list[i] and list[minIndex]
     return list[k]
该算法的其它一些优势在于:

(1)在定位到第j小的数后,便仅需要O(j + (k-j)2)的时间来定位第K小的数,或者当k <= j 时,仅需要O(k)的时间来提取第k小的数。

(2)该算法可以借助链表数据结构来实现。然而基于分割的方法则需要有直接存取的功能。

3、基于分割的通用选择算法

    一个在实际中很有效的通用的选择算法,但是在最坏情况下有较高时间复杂度的算法,是由快速排序的发明者C.A.R. Hoare构想出来的。该选择算法被称为Hoare's selection algorithm 或者被称为quickselect。

在快速排序算法中,有一个叫做分割的子过程,它能够在线性的时间内,将范围从left到right的列表分割为两个部分,即小于某一特定值的为一部分,大于等于某一特定值的为另一部分。下面的伪代码执行关于元素list[pivotIndex]的分割过程:

 function partition(list, left, right, pivotIndex)
     pivotValue := list[pivotIndex]
     swap list[pivotIndex] and list[right]  // Move pivot to end
     storeIndex := left
     for i from left to right
         if list[i] < pivotValue
             swap list[storeIndex] and list[i]
             increment storeIndex
     swap list[right] and list[storeIndex]  // Move pivot to its final place
     return storeIndex
    在快速排序算法中,我们递归的对两个分支进行排序,获得了最好的 Ω(n log n) 的时间复杂度。但是在选择问题中,我们只需要知道想要的元素在哪一个分支里面,因为枢纽元素处于最终排序完成的位置时,位于它左边的元素是有序的,位于它右边的元素也是有序的。因此,只需要在正确的分支里进行一次递归调用就可以找到所需的元素了。
function select(list, left, right, k)
     if left = right // If the list contains only one element
         return list[left]  // Return that element
     select pivotIndex between left and right
     pivotNewIndex := partition(list, left, right, pivotIndex)
     pivotDist := pivotNewIndex - left + 1 
     // The pivot is in its final sorted position, 
     // so pivotDist reflects its 1-based position if list were sorted
     if pivotDist = k 
         return list[pivotNewIndex]
     else if k < pivotDist 
         return select(list, left, pivotNewIndex - 1, k)
     else
         return select(list, pivotNewIndex + 1, right, k - pivotDist)

注意它与快速排序算法的相似之处:正如基于最小值的选择算法是一种部分选择算法一样,该方法是一种部分快速排序方法。相比于快速排序里O(n)的分割时间,这里仅需要O(logn)的时间。这个简单的算法有着线性的表现,正如快速排序一样,在实际应用中有着很好的表现。它同时也是一个in-place算法,仅需要常数级的内存开销,因为尾递归可以使用下面的方法来消除:

function select(list, left, right, k)
     loop
         select pivotIndex between left and right
         pivotNewIndex := partition(list, left, right, pivotIndex)
         pivotDist := pivotNewIndex - left + 1
         if pivotDist = k
             return list[pivotNewIndex]
         else if k < pivotDist
             right := pivotNewIndex - 1
         else
             k := k - pivotDist
             left := pivotNewIndex + 1
像快速排序算法一样,该算法对于枢纽元素 pivot的选择十分敏感。如果常常选择的是不好的枢纽元素,该算法将会退化到和之前所述的基于最小值的选择算法一样, 时间复杂度为 O(n2)。David Musser描述的“median-of-3 killer”序列能够使得著名的三数取中位数的枢纽元素选取方法失效。

4、线性通用选择算法--中位数的中位数算法

未完待续。。。



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值