快速排序实现以及优化

快速排序是目前使用最广的排序算法,Java的默认排序方法就是快速排序,其特点为原地排序(不需要辅助数组,节省了空间);而且具有较为优秀的排序时间复杂度 N l o g N NlogN NlogN

快排的思路

快速排序是一种分治思想的递归排序算法,它每次排序都把整个数组划分为两个子数组,然后递归的对数组进行排序,最终使递归下面的全部子数组排序,从而让整个数组有序。

常规思路:

  1. 以数组第一个元素为切分元素。

  2. 采用双指针,从数组的左边开始寻找大于等于切分元素的值,从数组的右边开始寻找小于等于切分元素的值,并且保证左指针小于右指针,然后交换两个值。

  3. 循环进行步骤2,直到两个指针碰撞,这样就保证了指针碰撞处左边所有元素小于切分元素,所有右边元素大于切分元素,然后将指针碰撞处的元素和第一个元素交换。

  4. 重复递归1,2,3过程,最终保证整个数组有序。

快排实现

public void quickSort(int[] a,int s,int e){
// 首先设立终止条件,判断起始点是否小于终止点,如果起始点大于等于终止点则直接返回
    if (s >= e) return;
    //取切分元素
    int index = a[s];
    //定义左右指针
    int low = s;
    int high = e;
    // 循环判断左指针和右指针的大小关系
    while (low < high){
    //循环寻找右边小于切分元素的值
        while (low < high && a[high] >= index){
            high--;
        }
        //最开始的左指针指向第一个元素,因此可以将之前寻找到的右边小于切分元素的值与左指针元素互换,此时有两个重复的元素,去掉了切分元素,后面会加进数组。
        if (low < high && a[high] < index){
            a[low++] = a[high];
        }
        //循环寻找左边大于切分元素的值
        while (low < high && a[low] <= index){
            low++;
        }
        // 将原本重复的数替换为找到的左边大于切分元素的值,这样就又多了一个重复元素。
        if (low < high && a[low] > index){
            a[high--] = a[low];
        }
    }
    // 当执行完上面循环后,一次递归就接近结束了,最后一次递归时数组中左右指针中的一个指向重复的值,另一个指针向这个指针靠近,但是由于两个指针碰撞跳出循环,所以此时两个指针都指向重复值。
    a[low] = index;
    //将重复值替换为切分元素后,进行递归
    quickSort(a,s,low-1);
    quickSort(a,low+1,e);
}

找到一个动图可以很形象的形容快排过程

算法改进

切分元素的随机选择

快排最糟糕的情况是需要进行递归 N − 1 N-1 N1次时间复杂度为 O l o g ( N 2 ) Olog(N^2) Olog(N2),为了避免这种情况,我们需要优化切分元素的选择,加入随机性。

int index = a[new Random().nextInt(e-s) + s];

经过测试

public static void main(String[] args){
    test t = new test();
    int[] a = new int[10000];
    for (int i = 0;i < 10000;i++){
        a[i] = 10000 - i;
    }
    long st = System.currentTimeMillis();
    t.quickSort(a,0,a.length-1);
    long et = System.currentTimeMillis();
    System.out.println(et - st);
}

在如上糟糕的初始数组中进行排序,如果不适用随机选取切分元素的方法,得到需要用时平均为39ms,而采用随机选择切分元素时,只需要平均2ms。

三向切分

当我们需要排序的数组中有大量的重复元素时,我们实现在快速排序在递归时会遇到大量的重复子数组,因此为了优化这种情况,我们对算法进行切分。

先随机选取一个切分元素,然后把数组切成大于,等于,小于这个元素的三个部分,一次递归可以排序好所有等于切分元素的值,然后单独排序小于切分元素的和大于切分元素的值。

修改为如下代码:

    public void quickSort(int[] a,int s,int e){
        if (s >= e) return;
//        int index = a[s];
        int index = a[new Random().nextInt(e-s) + s];
        int i = s+1;
        //此处左右指针用来指示小于和大于切分元素的值的区间
        int low = s;
        int high = e;
        //循环判断条件为遍历指针不能超过右指针,因为要保证右边全是大于切分元素的值
        while (i <= high){
        // 当遍历到的元素大于切分元素就与右指针指向的值换位置,反之则与左指针指向的值换位置,
        //但是由于是从左开始遍历,
        //因此如果和左指针互换的话必须左指针和遍历指针同时增加,要保证左指针和遍历指针不会重叠。
        //如果是和切分元素相等,则遍历指针增加,因为后面这个相同的值会被左指针指到,然后被换到遍历指针的地方。
        //而被换到左指针的值必定是小于切分元素的值,这样就将与切分元素相等的值都集中到了中间部分。
            if (i <= high && a[i] > index){
                int tmp = a[high];
                a[high--] = a[i];
                a[i] = tmp;
            }else if (i <= high && a[i] < index){
                int tmp = a[low];
                a[low++] = a[i];
                a[i++] = tmp;
            }else if (i <= high && a[i] == index){
                i++;
            }
        }
        //递归左边小于切分元素的数组和右边大于切分元素的数组。
        quickSort(a,s,low-1);
        quickSort(a,low+1,e);
    }

使用这样的代码进行测试:

public static void main(String[] args){
    test t = new test();
    int[] a = new int[10000];
    for (int i = 0;i < 2000;i++){
        a[i] = 5444;
    }
    for (int i = 2000;i < 5000;i++){
        a[i] = 333;
    }
    for (int i = 5000;i<10000;i++){
        a[i] = 556;
    }
    long st = System.currentTimeMillis();
    t.quickSort(a,0,a.length-1);
    long et = System.currentTimeMillis();
    System.out.println(et - st);
}

发现对数组进行三向切分后的算法,运行时间平均为1ms,而不加入三向切分则需要用时44ms。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值