码迷,mamicode.com
首页 > 编程语言 > 详细

常用算法之排序算法三【快速排序】

时间:2015-04-13 18:59:23      阅读:173      评论:0      收藏:0      [点我收藏+]

标签:排序算法   快速排序   

快速排序是东尼·霍尔在1962提出的划分交换排序,并采用一种分治的策略。在这,我们先总结一下:快速排序 = 划分交换排序 + 分治。然后我们在一一介绍他。

划分交换排序

在讨论它时,感觉有种看了崔颢《黄鹤楼》之后,再去写黄鹤楼的感觉,因为MoreWindows写 得白话经典算法系列之六 快速排序 快速搞定已经足够出色了。我在这只是进行简单的复述,你需要了解更多请看他的博文。

先来看看划分交换排序的具体算法描述:

1.从数列中选出一个数作为基准

2.从数组中选出比它大的数放右边,比它小的数放左边

3.重复以上操作,直至只剩最后一个数

看了算法描述呢,感觉还是有点晦涩,我们可以换过一种思考方式:挖坑填数。

我们先来看看具体实例:

给出一个数组,并将第一个数作为基准

 0

  1

2

3

4

5

6

7

8

9

72

6

57

88

60

42

83

73

48

85


初始化时,i = 0, j = 9; X = array[0] = 72

由于array[0] 已经保存到X中,所以,可以理解已经在i = 0处,挖好了一个坑,可以将其他的数据填充到这个坑中。

接下来充由j位置从后往前进行查找小于等于基准的值,在j = 8处48符合条件,这时我们将它挖出,并填到上一个坑处,即array[0] = array[8];i++;此时,我们填好了一个坑,但是又重新生成了一个新坑,该怎么办呢。我们依旧需要找一个其他的值将它填上,接下来我们在i的位置从前往后找,直至找到大于等于基准的值。当i = 3;时,符合条件,将array[8] = array[3];j--;数组便变成了:

0

 1

2

3

4

5

6

7

8

9

48

6

57

88

60

42

83

73

88

85


此时i = 3, j = 7

重复以上操作,先从后往前找,在从前往后找。

从j开始往前找,在j = 5处的42小于基准值,将它填到i = 3处,array[3] = array[5]; i++;

从i = 4处开始从前往后找,当i = 5时依旧没有符合条件的值,且(i<j)不满足,所以应该跳出循环,退出。

此时 i=5,且j = 5,整个数组剩下一个坑就是i = 5时,因为第一次的划分已经完毕,所以讲基准值填回坑中,即array[i]=X;


数组变成:

0

 1

2

3

4

5

6

7

8

9

48

6

57

42

60

72

83

73

88

85


可以看出,i=5之前的值全部小于或等于array[5],之后的值大于等于array[5]。

请注意,不是每次的划分都会是这么理想,当所以怎么选择基准会成为影响效率的一大原因,其次因为每次是找出大于等于或小于等于的值进行填坑,所以该算法是不稳定的。

接下来,我们将数组分为两个集合p1 = array[0...4], p2 = array[6...9],那么我们先来看看这段思想化成的代码吧。

    public static void sort(int[] array, int left, int right){
        int i = left;
        int j = right;

        if (i >= j){
            return;
        }
        int pivot = array[i];

        while (i < j){
            while ((i < j) && (array[j] > pivot)){
                j--;
            }
            if (i < j){
                array[i] = array[j];
            }

            while ((i < j) && (array[i] < pivot)){
                i++;
            }
            if (i < j){
                array[j] = array[i];
            }
        }
        array[i] = pivot;
   }


然后再划分好之后继续如此,直至集合中元素小于等于1个,好好想想,这个有没有像什么,没错,树,小的放左边,大的放右边,依照方法想,我们可以想到递归。好了下来,就是我们的分治了。

分治法

  在这,不做详细的介绍,假如有兴趣,推荐博客 五大常用算法之一:分治算法

分治算法最简单的解释就是:将一个问题分解成多个小问题,再递归解决所有的问题,最后将解合并,生成最终解。

他的算法模式:

    Divide-and-Conquer(P)

    1. if |P|≤n0

    2. then return(ADHOC(P))

    3. 将P分解为较小的子问题 P1 ,P2 ,...,Pk

    4. for i←1 to k

    5. do yi ← Divide-and-Conquer(Pi) △ 递归解决Pi

    6. T ← MERGE(y1,y2,...,yk) △ 合并子问题

    7. return(T)
  分治在快排中的具体思想:

在一个给定的一个数组R[left....right]中,先从数组中选取一个元素作为基准pivort,根据基准,可以将数组分为两个子数组R[left....pivortpos-1]和R[pivortpost+1....right]。根据定义,可以知道,左边的元素全部小于等于基准pivort,右边的元素全部大于等于pivort,所以pivort已经在正确的位置上了,在接下来的递归中,不需要在对它进行划分。

划分的关键是求出基准所在的位置pivortpos,划分的结果可以简单表示为pivort = R[pivortpos];换分的结果可以简单表示为:

R[left...pivortpos-1] <= pivort <= R[pivortpos+1....right]

left <= pivortpos <= right

所以在我们可以写出分治的部分代码

    public static void sort1(int[] array, int left, int right){
        if (left >= right){
            return;
        }
        int i = left;
        int j = right;
        int pivort = array[i];
        while(i < j){
            //划分交换
            int pivortpos = Partition(R, left, right);
            sort(array, left, pivortpos-1);
            sort(array,pivortpos+1, right);
        }
    }

所以,我们可以将两个代码结合,得到完整的代码:

    public static void sort(int[] array, int left, int right){
        int i = left;
        int j = right;

        if (i >= j){
            return;
        }
        int pivot = array[i];

        while (i < j){
            while ((i < j) && (array[j] > pivot)){
                j--;
            }
            if (i < j){
                array[i] = array[j];
            }

            while ((i < j) && (array[i] < pivot)){
                i++;
            }
            if (i < j){
                array[j] = array[i];
            }
        }
        array[i] = pivot;
        sort(array, left, i-1);
        sort(array, i+1, right);
    }
说完了,但是可能还是感觉有点不清楚,那我们继续来看一下他的执行过程:

首先可以大家可以先看个动画演示,是关于划分交换的,当然假如你觉得自己足够清楚,可以跳过;
假如我们存在以下集合:

技术分享


在这我们可以看到,他并不是稳定的排序算法,并且他的效率和基准的选择有很大的关系。

总结:

快速排序的最坏时间复杂度是O(n^2),最好时间复杂度为O(nlogn),平均时间复杂度为O(nlogn),他是基于关键字比较的内部排序算法中速度最快的。

空间复杂度:因为是递归树的高度O(logn),所以需要栈空间O(logn),最差时是O(n)。

常用算法之排序算法三【快速排序】

标签:排序算法   快速排序   

原文地址:http://blog.csdn.net/u010233260/article/details/45021057

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!