三.快速排序

快速排序

快速排序使用了分治法策略,把一个数组分成两个数组,对数组arr[p…r]进行快速排序的三步分治过程:
分解:数组被分成两个(可能为空)子数组arr[p…q-1]跟arr[q+1…r],其中arr[p…q-1]的所有元素都小于等于arr[q],arr[q+1…r]中的所有元素都大于arr[q],并计算出下标q的数值。
解决:通过递归调用快速排序,对子数组arr[p…q-1]跟arr[q+1…r]进行排序。
合并:并不需要合并,因为都是对数组本身排序的,数组arr[p…r]已经有序。
先进行算法的分解步骤:
选择数组的其中一个元素为基准(这里选择最后一个元素),使用一个数来记录最靠左(也就是下标最小的)比基准大的数的下标,用index表示,它的初始值为数组的开始下标,然后从左到右地把数组的元素跟基准进行比较,如果比基准大的话,不做任何处理,继续比较下一个数,当出现小于基准的数时,把该数跟arr[index]进行交换,然后index加1,重复该过程,直到把数组所有元素(除了基准数)都跟基准比较完,把arr[index]跟基准互换,返回index。
举个例子:
以数组{42,1,27,33,11,33,26,1,43,28}为例:
数组1
基准数reference=28,index=0,首先数组的第一个元素跟28比较,42>28,不做处理,继续比较第二个数,1<28,此时index=0,42跟1交换,然后index=1,得到:
数组2
继续下一个数,27<28,42跟27交换,index=2,得到
数组3
继续下一个数,33>28,不做处理,继续下一个数与28比较,11<28,此时index=2,42与11交换,index=3,得到:
数组4
重复该过程,得到:
数组5
此时arr[index]是比基准大,且是最靠左的数,把arr[index]跟基准互换,得到:
数组6
就这样,把比28小的放到28前面,大的放到28后面,把原来的数组分成{1,27,11,26,1}跟{33,42 ,43,33}两个子数组。
该过程的Java代码实现:

    public static int partition(int[] arr, int left, int right){
        int reference = arr[right];
        int index = left;
        for (int i = left; i < right; i++){
            if (arr[i] < reference){
                int temp = arr[i];
                arr[i] = arr[index];
                arr[index] = temp;
                index = index + 1;
            }
        }
        arr[right] = arr[index];
        arr[index] = reference;
        return index;
    }

对于规模为n的数组,for循环需要循环n-1次,最坏的情况下进行n-1次数组元素的互换,其他语句需要常数的时间,杂度为使用partitio解决间复程n)。
接下来是解决的过程,递归地的调用快速排序,Java代码为:

public static void quickSort(int[] arr, int start, int end)
    {
        if (start < end)
        {
            int q = partition(arr, start, end);
            quickSort(arr, start, q - 1);
            quickSort(arr, q + 1, end);
        }
    }

最终的Java代码为:

public static void quickSort(int[] arr, int left, int right)
    {
        if (left < right)
        {
            int reference = arr[right];
            int index = left;
            for (int i = left; i < right; i++){
                if (arr[i] < reference){
                    int temp = arr[i];
                    arr[i] = arr[index];
                    arr[index] = temp;
                    index = index + 1;
                }
            }
            arr[right] = arr[index];
            arr[index] = reference;
            quickSort2(arr, left, index - 1);
            quickSort2(arr, index + 1, right);
        }
    }
  1. 时间复杂度
    开始排序的运行时间取决于基准元素对数组划分是否平衡,如果划分是平衡的,那么快速排序的时间复杂度跟归并排序一样是O(nlog2n),而越不平衡,算法的性能就越接近插入排序,而插入排序的时间复杂度为O(n平方)。如果每次划分都是最坏的情况的话,也就是划分出来的两个子数组分别为n-1个和0个,此时快速排序的时间复杂度为O(n平方)。
    如果用快速排序来排序已经有序的数组,时间复杂度也仍然为O(n平方),以上的实现对此情况的表现非常差,因为基准已经是最大的数,前面的数都是小于等于基准的数,其中小于基准的数都要与自身交换。
  2. 空间复杂度
    快速排序具有空间原址性(任何时候都只需要常数个额外的元素空间存储临时的数据)。
    测试:
    排序效果
    接下来分别使用10000,100000,500000个随机数进行测试:
    这里写图片描述

当然,还有其它的实现方式:

    public static void quickSort(int[] arr, int left, int right){
        if (left < right){
            int i = left, j = right, x = arr[right];
            while (i < j){
                while (i < j && arr[i] < x)
                    i++;
                if (i < j)
                    arr[j--] = arr[i];

                while (i < j && arr[j] >= x)
                    j--;
                if (i < j)
                    arr[i++] = arr[j];
            }
            arr[i] = x;
            quick_sort(arr, left, i - 1);
            quick_sort(arr, i + 1, right);
        }
    }

参考C#Array.Sort()的快速排序的实现:

public static void quickSort(int[] keys, int left, int right){
        do{
            int a = left;
            int b = right;
            int num3 = keys[right];
            do{
                while (keys[a] < num3){
                    a++;
                }
                while (num3 < keys[b]){
                    b--;
                }
                if (a > b){
                    break;
                }
                if (a < b){
                    int local2 = keys[a];
                    keys[a] = keys[b];
                    keys[b] = local2;
                }
                a++;
                b--;
            }
            while (a <= b);
            if ((b - left) <= (right - a)){
                if (left < b){
                    quickSort(keys, left, b);
                }
                left = a;
            }else{
                if (a < right){
                    quickSort(keys, a, right);
                }
                right = b;
            }
        }
        while (left < right);
    }

在测试时,使用的是随机的数组,但在实际工作中,输入的数据的所有排列并不一定是等概率的, 通过在算法中引入随机性,从而使得算法对所有的输入都能获得较好的期望性能,快速排序的随机化版本比较适合大数据情况下的排序算法,Java代码实现:

public static void quickSort_random(int[] arr, int left, int right){
        if (left < right){
            int random = (int)((Math.random()*(right - left + 1)) + left);
            int reference = arr[random];
            arr[random] = arr[right];
            arr[right] = reference;
            int index = left;
            for (int i = left; i < right; i++){
                if (arr[i] < reference){
                    int temp = arr[i];
                    arr[i] = arr[index];
                    arr[index] = temp;
                    index = index + 1;
                }
            }
            arr[right] = arr[index];
            arr[index] = reference;
            quickSort_random(arr, left, index - 1);
            quickSort_random(arr, index + 1, right);
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值