~~~~ 快速排列,又称划分交换排序,简称快排,一种排序算法。在平均状况下,排序 n n n个项目要 O ( n l o g n ) O(nlog~n) O(nlog n)次比较。在最坏状况下则需要 O ( n 2 ) O(n^2) O(n2)次比较,但这次状况并不常见。事实上,快速排序 Θ ( n l o g n ) \Theta(n~log~n) Θ(n log n)通常明显比其他算法更快,因为它的内部循环可以在大部分的架构上很有效率的达成。
算法
~~~~
快速排序使用分治法策略来吧一个序列分为较小和较大的两个子序列,然后递归地排序两个子序列。
步骤为:
1.挑选基准值:从数组中挑出一个元素,称为“基准”,
2.分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以放到任何一边)。在这个分割结束之后,对基准值的排序已经完成,
3.递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列。
~~~~
递归到最底部的判断条件是数列的大小是零或一,此时该数列显然已经有序。
选取基准值有数种具体方法,次选取方法对排序的时间性能有决定性影响。
在简单的伪代码中,此算法可以被表示为:
function quicksort(q)
{
var list less, pivotList, greater
if length(q) ≤ 1
return q
else
{
select a pivot value pivot from q
for each x in q except the pivot element
{
if x < pivot then add x to less
if x ≥ pivot then add x to greater
}
add pivot to pivotList
return concatenate(quicksort(less), pivotList, quicksort(greater))
}
}
原地(in-place)分割版本
上面简单版本的缺点是,他需要
Ω
(
n
)
\Omega (n)
Ω(n)的额外存储空间,也就是跟归并排序一样不好。额外需要的存储器空间配置,在实际上的实现,也会极度影响速度和缓存的性能。有一个比较复杂使用原地分割算法的版本,且在好的基准选择上,平均可以达到
Ω
(
l
o
g
n
)
\Omega (logn)
Ω(logn)空间的使用复杂度。
function partition(a, left, right, pivotIndex)
{
pivotValue = a[pivotIndex]
swap(a[pivotIndex], a[right]) // 把pivot移动到结尾
storeIndex = left
for i from left to right-1
{
if a[i] <= pivotValue
{
swap(a[storeIndex], a[i])
storeIndex = storeIndex + 1
}
}
swap(a[right], a[storeIndex]) // 把pivot移动到它最后的地方
return storeIndex
}
这里原地分割算法,它分割了标示为left和right的序列部分,借由移动小于a[pivotIndex]的所有元素到子序列的开头,留下大于或者等于的元素接在它们后面。在这个过程它也可为基准元素找寻最后摆放的位置,也就是它回传的值。它暂时地把基准元素移到子序列的结尾,而不会被前述方式影响到。由于算法只是用交换,因此最后的数列与原先的数列拥有一样的元素。要注意的是,一个元素在到达它的最后位置前,可能会被交换很多次。
一旦我们有了这个分割算法,要写快速排序本身就很容易了:
procedure quicksort(a, left, right)
if right > left
select a pivot value a[pivotIndex]
pivotNewIndex := partition(a, left, right, pivotIndex)
quicksort(a, left, pivotNewIndex-1)
quicksort(a, pivotNewIndex+1, right)
维基百科:快速排序