快速排序算法原理

转载自:http://bubkoo.com/2014/01/12/sort-algorithm/quick-sort/

算法原理

快速排序是图灵奖得主 C. R. A. Hoare 于 1960 年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)

C. R. A. HoareC. R. A. Hoare

分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。

利用分治法可将快速排序的分为三步:

  1. 在数据集之中,选择一个元素作为”基准”(pivot)。
  2. 所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。这个操作称为分区 (partition) 操作,分区操作结束后,基准元素所处的位置就是最终排序后它的位置。
  3. 对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
    图片来自维基百科图片来自维基百科分区是快速排序的主要内容,用伪代码可以表示如下:
     
     
1
2
3
4
5
6
7
8
9
10
     
     
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 // 返回 pivot 的最终位置

首先,把基准元素移到結尾(如果直接选择最后一个元素为基准元素,那就不用移动),然后从左到右(除了最后的基准元素),循环移动小于等于基准元素的元素到数组的开头,每次移动 storeIndex 自增 1,表示下一个小于基准元素将要移动到的位置。循环结束后 storeIndex 所代表的的位置就是基准元素的所有摆放的位置。所以最后将基准元素所在位置(这里是 right)与 storeIndex 所代表的的位置的元素交换位置。要注意的是,一个元素在到达它的最后位置前,可能会被交换很多次。

一旦我们有了这个分区算法,要写快速排列本身就很容易:

     
     
1
2
3
4
5
6
     
     
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)

实例分析

举例来说,现有数组 arr = [3,7,8,5,2,1,9,5,4],分区可以分解成以下步骤:

  1. 首先选定一个基准元素,这里我们元素 5 为基准元素(基准元素可以任意选择):
     
     
1
2
3
     
     
pivot
3 7 8 5 2 1 9 5 4
  1. 将基准元素与数组中最后一个元素交换位置,如果选择最后一个元素为基准元素可以省略该步:
     
     
1
2
3
     
     
pivot
3 7 8 4 2 1 9 5 5
  1. 从左到右(除了最后的基准元素),循环移动小于基准元素 5 的所有元素到数组开头,留下大于等于基准元素的元素接在后面。在这个过程它也为基准元素找寻最后摆放的位置。循环流程如下:

    循环 i == 0 时,storeIndex == 0,找到一个小于基准元素的元素 3,那么将其与 storeIndex 所在位置的元素交换位置,这里是 3 自身,交换后将 storeIndex 自增 1,storeIndex == 1:

           
           
    1
    2
    3
    4
    5
           
           
    pivot
    3 7 8 4 2 1 9 5 5
    storeIndex

    循环 i == 3 时,storeIndex == 1,找到一个小于基准元素的元素 4:

           
           
    1
    2
    3
    4
    5
           
           
    ┌───────┐ pivot
    ↓ ↓ ↓
    3 7 8 4 2 1 9 5 5
    ↑ ↑
    storeIndex i

    交换位置后,storeIndex 自增 1,storeIndex == 2:

           
           
    1
    2
    3
    4
    5
           
           
    pivot
    3 4 8 7 2 1 9 5 5
    storeIndex

    循环 i == 4 时,storeIndex == 2,找到一个小于基准元素的元素 2:

           
           
    1
    2
    3
    4
    5
           
           
    ┌───────┐ pivot
    ↓ ↓ ↓
    3 4 8 7 2 1 9 5 5
    ↑ ↑
    storeIndex i

    交换位置后,storeIndex 自增 1,storeIndex == 3:

           
           
    1
    2
    3
    4
    5
           
           
    pivot
    3 4 2 7 8 1 9 5 5
    storeIndex

    循环 i == 5 时,storeIndex == 3,找到一个小于基准元素的元素 1:

           
           
    1
    2
    3
    4
    5
           
           
    ┌───────┐ pivot
    ↓ ↓ ↓
    3 4 2 7 8 1 9 5 5
    ↑ ↑
    storeIndex i

    交换后位置后,storeIndex 自增 1,storeIndex == 4:

           
           
    1
    2
    3
    4
    5
           
           
    pivot
    3 4 2 1 8 7 9 5 5
    storeIndex

    循环 i == 7 时,storeIndex == 4,找到一个小于等于基准元素的元素 5:

           
           
    1
    2
    3
    4
    5
           
           
    ┌───────────┐ pivot
    ↓ ↓ ↓
    3 4 2 1 8 7 9 5 5
    ↑ ↑
    storeIndex i

    交换后位置后,storeIndex 自增 1,storeIndex == 5:

           
           
    1
    2
    3
    4
    5
           
           
    pivot
    3 4 2 1 5 7 9 8 5
    storeIndex
  2. 循环结束后交换基准元素和 storeIndex 位置的元素的位置:

     
     
1
2
3
4
5
     
     
pivot
3 4 2 1 5 5 9 8 7
storeIndex

那么 storeIndex 的值就是基准元素的最终位置,这样整个分区过程就完成了。

引用维基百科上的一张图片:

图片来自维基百科图片来自维基百科

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值