vlambda博客
学习文章列表

查找与排序:快速排序

快速排序

我们现在再看看快速排序。
快速排序采用的是分而治之的原则,基本思路是,随便先选择一个数比如第一个数,以它为基准,按照一定的办法把比它小的放到它前面,比它大的放到它后面,这样整个数据序列就整体分成了小和大两堆,然后在每一堆里面继续这个操作。
我们看一个例子,假定手头有这么一个数字序列:

0

1

2

3

4

5

6

7

8

9

72

6

57

88

60

42

83

73

48

85

我们先把第一个数72拿出来,以它为基准去比对并分成两堆。
pivot = 72
拿出来之后,数字序列就出来了一个空。形如下:

0

1

2

3

4

5

6

7

8

9


6

57

88

60

42

83

73

48

85

我们现在就要去在序列中找一个合适的数字填空。按照这个办法找,我们拿这个基准数是从最左边拿的,现在空也在最左边,我们就从最右边开始找数。
最右边位置9的数是85,比基准数72要大,我们不用管它,位置减1,往左移动一格,得到位置8的数,是48,比72小,我们就把这个数拿出来填空。形如:

0

1

2

3

4

5

6

7

8

9

48

6

57

88

60

42

83

73


85

这个时候,空就换到了位置8了。我们再反向从左往右找,从位置1开始(因为位置0已经是填好空了,不用管了),这个数是6,比72小,不用管,位置加1,往右移动一格,得到位置2的数,是57,还是比72小,不用管,位置加1,往右移动一格,得到位置3的数,是88,比72大,就把这个数拿出来填空,形如:

0

1

2

3

4

5

6

7

8

9

48

6

57


60

42

83

73

88

85

这个时候,空就换到了位置3了。我们再反向从右往左找,从位置7开始(因为位置8已经是填好空了,不用管了),这个数是73,比72大,不用管,位置减1,往左移动一格,得到位置6的数,是83,比72大,不用管,位置减1,往左移动一格,得到位置5的数,是42,比72小,就把这个数拿出来填空,形如:

0

1

2

3

4

5

6

7

8

9

48

6

57

42

60


83

73

88

85

这个时候,空就换到了位置5了。我们再反向从左往右找,从位置4开始(因为位置3已经是填好空了,不用管了),这个数是60,比72小,不用管位置加1,往右移动一格,位置为5,碰到了空格,于是这一遍就结束了。我们把72放到位置5。形如:

0

1

2

3

4

5

6

7

8

9

48

6

57

42

60

72

83

73

88

85

到此,整个数列,位置5左边的都比72小,右边的都比72大,所以呢,成了局部有序的了。我们接下来就按照同样的办法把位置0-4的子序列排序,把位置6-9的子序列排序。

0

1

2

3

4

48

6

57

42

60

pivot=48
拿出来48,有了空,形如:

0

1

2

3

4


6

57

42

60

从右边开始比对,到了位置3,数为42,比48小,填空,形如:

0

1

2

3

4

42

6

57


60

再从左边开始比对,到了位置2,数为57,比48大,填空,形如:

0

1

2

3

4

42

6

48

57

60

就按照这个递归的办法这么一步一步的排序。
从程序实现上,我们是按照递归一遍一遍执行的,对每一遍里面来讲,我们需要三个标记,一个标记空的位置,一个标记左限,一个标记右限。
我们先看怎么实现第一遍:

    arr[pivotIndex],arr[left]=arr[left],arr[pivotIndex]
def partition(arr,start,end):
    pivotIndex = start #初始空,在最左边
    pivot = arr[start#基准值初始化为最左边的数
    left = start+1
    right = end
    while right>=left:
        while right>=left and arr[right]>=pivot#从右开始逐个比对,大就继续比对
            right-=1
        if right>=left#右边找到了一个小于基准值的,交换填空
            swap(arr,pivotIndex,right)
            pivotIndex=right
            right-=1
        while left<=right and arr[left]<=pivot:#从左开始逐个比对,小就继续比对
            left+=1
        if left<=right:#左边找到了一个大于基准值的,交换填空
            swap(arr,pivotIndex,left)
            pivotIndex=left
            left+=1
return pivotIndex

程序对一个列表的某一段范围的数列按照某个基准值进行分割,解释已经写到代码里面了。交换数据的语句:

arr[pivotIndex],arr[left]=arr[left],arr[pivotIndex]

这个是Python比较特殊的表达。
测试一下:

a = [72,6,57,88,60,42,83,73,48,85]
i=partition(a,0,len(a)-1)
print(i)
print(a)

运行结果为:

5
[4865742607283738885]

你可以一步步跟踪,看这一遍是怎么填空的。
现在有了某一遍的程序,在外面再套一个递归,把过程演练完:

def quicksort(arr,start,end):
    index = partition(arr, startend)
    if index > start:
        quicksort(arr, startindex - 1)
    if index < end:
        quicksort(arr, index + 1end)
    return arr

测试一下:

a = [72,6,57,88,60,42,83,73,48,85]
print(quicksort(a,0,len(a)-1))

运行结果为:

[6424857607273838588]

快速排序的一次划分算法从两头交替搜索,直到low和high重合,因此其时间复杂度是O(n);而整个快速排序算法的时间复杂度与划分的趟数有关。理想的情况下,每次划分所选择的中间数恰好将当前序列几乎等分,经过log2n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O(nlog2n)。但是如果运气不好,最坏的情况是,每次所选的中间数是当前序列中的最大或最小元素(也就是序列本来就是基本有序的),那就退化成O(n2)
平均而言,快速排序,确实性能提高了很多。这个算法被誉为二十世纪七大算法之一。
快速排序由英国著名科学家Hoare发明。他是一个谦逊的绅士,曾经说自己一生唯一有意义的工作就是这个算法。  

点击“阅读原文”,可进入原创书籍,阅读目录