快速排序

  #返回上一级

@Author: 张海拔

@Update: 2014-04-30

@Link: http://www.cnblogs.com/zhanghaiba/p/3515078.html

 

我们知道,三种基本排序算法(冒泡、选择、插入)可以看做减治算法(待排序规模规模不增减小)

而快速排序和归并排序是基于分治的思想,当你画出递归树的时候,容易看出每趟排序的效率均为O(n),而规模并不线性递减,而是以lg(n)的速度递减;

这也正是这两种基于分治思想排序算法的高效之处。

 

快速排序属于交换排序方法,可以说是对冒泡排序的改进。

首先你必须知道分治法,通过“冒泡”(划分过程),使得枢纽元素p的左边元素都<=p,右边元素都>=p。此时枢纽元素的最终位置已经完全确定。

一次划分后,左边部分的数组和右边部分部分的数组都是一个规模变小的原问题。因此,用递归处理这个问题是很自然的事情。

这种二分递归的场景,很容易联系到二叉树的遍历。

其实,快排过程就可以画一棵二叉树来描述,树没有扩展时是一个数组,扩展后(划分完毕)则树根是已经确定位置的元素。

那么,整个过程就是二叉树的前序遍历,在访问当前根之前就进行数组划分(即树的扩展)。

当你画出了快速排序的二叉树,不难分析出效率——

假定树的严格平衡的,那么每一层的划分遍历效率都是O(n),而二叉树的高度是logn,所以快速排序的最好效率是O(n*logn)。

 

至于划分函数,有很多的实现方法。比较朴素的实现就是大量通过swap()函数,毕竟快速排序一般归类为交换排序方法。

划分函数对应着一趟排序,这个过程可以看做“冒泡”,但这个泡在左右跳跃,并且幅度越来越小,最后停在一个位置,左边都比这个泡“轻”,右边都比这个泡“重”。

我这里的划分函数优化了交换过程,效率会更好一些。

我们知道,递归的划分目标是:使得一个元素p,使得其左边的元素均<=p,右边的元素均>=p。该元素p成为枢纽元素。

假定待排序数组是随机的,那么中枢元素选择数组最左边的元素即可。

首先,保存当前数组的最左元素a[l]为p。

然后,设置两个指针i, j,分别指向当前数组最左元素a[l],和最右元素a[r];

最初,a[i]是一个可以被覆盖的元素(其值保存在p中),所以我们从数组的最右端开始扫描(j--),目的是找到比p小的首个元素a[j],该元素的值保存到a[i]元素中;

但是这可以保证a[i]是落在p元素的左边吗?显然我们首先需要始终保证i < j,并且a[i]被覆盖后,a[j]就是一个可以被覆盖的元素了。

此时需要从a[i+1]开始往左扫描(i++),目的是找到比p大的首个元素a[i], 以覆盖a[j],这个过程的中止条件是i == j。

中止的条件,也满足数组只有一个元素的情景。这个位置的元素值就是枢纽元素的值,此时执行a[i] = p;

上述过程可以抽象成一个devide函数如下:

int devide(int *array, int left, int right)

{

    int i = left, j = right; p = array[left];

    while (i < j) {

        while (i < j && a[j] >= p)

            ++j;

        if (i < j) // 执行判断前,一定有a[j] < p

            a[i++] = a[j];

        while (i < j && a[i] <= p)

            ++i;

        if (i < j) // 执行判断前,一定有a[i] > p

            a[j--] = a[i];

    }

    // 跳出循环,则一定满足i == j

    a[i] = p;

    return i;

}

有了devide函数,那快排的主逻辑就更清晰了:

void quick_sort(int *array, int left, int right)

{

    if (left >= right)

        return;



    int mid = devide(array, left, right);



    quick_sort(array, left, mid-1);

    quick_sort(array, mid+1, right);

}

 

一般来说,快速排序作为一个抽象元就可以了,devide抽象出来就有些过度了。最终quick_sort Demo如下:

 1 /*

 2  *Author: ZhangHaiba

 3  *Date: 2014-1-11

 4  *File: quick_sort.c

 5  *

 6  *a quick sort demo

 7  *UpDate: 2014-4-30

 8  */

 9 

10 #include <stdio.h>

11 #define N 512

12 

13 //interface

14 void quick_sort(int* array, int left, int right);

15 void set_array(int* array, int num);

16 void show_array(int* array, int num);

17 

18 

19 int array[N];

20 

21 int main(void)

22 {

23     int n;

24 

25     scanf("%d", &n);

26     set_array(array, n);

27     quick_sort(array, 0, n-1);

28     show_array(array, n);

29     return 0;

30 }

31 

32 void quick_sort(int *a, int l, int r)

33 {

34     if (l >= r)

35         return;

36 

37     int i = l, j = r, p = a[l];

38 

39     while (i < j) {

40         while (i < j && a[j] >= p)

41             --j;

42         if (i < j)

43             a[i++] = a[j];

44         while (i < j && a[i] <= p)

45             ++i;

46         if (i < j)

47             a[j--] = a[i];

48     }

49     a[i] = p;

50 

51     quick_sort(a, l, i-1);

52     quick_sort(a, i+1, r);

53 }

54 

55 

56 void set_array(int *a, int n)

57 {

58     int i;

59     

60     for (i = 0; i < n; ++i)

61         scanf("%d", a+i);

62 }

63 

64 void show_array(int *a, int n)

65 {

66     int i;

67     

68     for (i = 0; i < n; ++i)

69         printf(i == n-1 ? "%d\n" : "%d ", a[i]);

70 }

 

最后我们再看看K&R的一个quick_sort实现

 1 void quick_sort_KandR(int *a, int left, int right)

 2 {

 3     if (left >= right) return;

 4     int last = left, i = left+1;

 5     for (i <= right; ++i)

 6         if (a[i] < a[left])

 7             swap(a, ++last, i);

 8     swap(v, left, last);

 9     quick_sort_KandR(a, left, last-1);

10     quick_sort_KandR(a, last+1, right);

11 }

/*
我们要使a[last]元素左边均<=它,右边均>=它
首先,我们设中枢元素为a[left],
然后,我们使a[left+1~last]中元素均<= a[left], a[last+1~right]均>=a[left]
最后,交换a[left]与a[last]即可。
a[last]为向右遍历过程中最后出现的<=a[left]的元素,为了不覆盖已知的<=p[left]元素,所以++last
*/

 

 

 #返回上一级 

你可能感兴趣的:(快速排序)