“选择算法”

来源:互联网 发布:网络大电影杀小姐 编辑:程序博客网 时间:2024/05/18 01:22

翻译自维基百科: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、线性通用选择算法--中位数的中位数算法

未完待续。。。



原创粉丝点击