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