快速排序

java实现

Posted by Starboyate on June 22, 2019

一、前言

排序算法是我们学程序最经典最基础的算法,所以我们应该熟练的掌握排序算法


二、快速排序

1.什么是快速排序呢?

wiki定义:快速排序,又称划分交换排序,简称快排,一种排序算法,最早由东尼·霍尔提出。 在平均状况下,排序个项目要次比较。在最坏状况下则需要次比较,但这种状况并不常见。 事实上,快速排序通常明显比其他算法更快,因为它的内部循环可以在大部分的架构上很有效率地达成

2.快速排序算法描述

每次选择一个元素作为分界点,然后通过递归算法进行划分,把小于分界点的元素放置分界点左边, 大于分界点的元素放置分界点右边,循环此操作,直至数组待排序数组不可划分为止。


快速排序

3.第一版快速排序算法实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class QuickSort {
    public static void sort(int[] nums) {
        // 调用辅助函数,进行真正的逻辑
        sort(nums, 0, nums.length - 1);
    }

    private static void sort(int[] nums, int left, int right) {
        // 当left >= right就代表已经全部划分完了,所以这是终止条件
        if (left >= right) {
            return;
        }
        // 进行划分操作
        int p = partition(nums, left, right);
        sort(nums, left, p - 1);
        sort(nums, p + 1, right);
    }

    private static int partition(int[] nums, int left, int right) {
        // 选择第一个元素作为分界点
        int e = nums[left];
        // 记录每次对比的元素的想赢下标
        int j = left;
        for (int i = left + 1; i <= right; i ++) {
            // 比分界点小的元素我们才需要交换为止,因为比分界点大的元素本来就是在分界点的右边
            if (nums[i] < e) {
                swap(nums, j + 1, i);
                j ++;
            }
        }
        // 记录了分界点应该存在的下标,然后和第一个下标也就是left进行交换
        swap(nums, left, j);
        return j;
    }

    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}


通过上面的算法实现,很容易清晰的看出快排的时间复杂度是O(nlogn),但是,是不是上面的实现就已经没有问题了呢?

  • 1.首先上面的快速排序我们采用的是选取每次进行partition操作部分数组的第一个元素作为分界点,这样会存在什么问题呢? 如果我们数组元素位置是 9 8 7 6 5 4 3 2 1,是不是每次进行partition操作都对应的分界点永远不是一个中间的数,那这种情况就会退化成O(n^2)
  • 2.当我们排序的数组存在大量的重复的元素,按照现在实现的算法,相同元素是会被频繁移动和划分,使得我们算法速度变慢。 下面我们来基于这两点就行算法优化


4.三路快速排序

什么是三路快速排序?

其实就是把待排序的数组分为三部分:小于-等于-大于,这样子相同元素部分就可以避免没必要的移动交换了。

1.算法实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class QuickSort {
    public static void sort(int[] nums) {
        // 调用辅助函数,进行真正的逻辑
        sort(nums, 0, nums.length - 1);
    }

    private static void sort(int[] nums, int left, int right) {
        // 当left >= right就代表已经全部划分完了,所以这是终止条件
        if (left >= right) {
            return;
        }
        // 第一点优化: 随机选取一个元素作为我们的临界点,为了解决选取元素平衡问题
        swap(nums, left, (int)Math.random() * (right - left + 1) + left);
        int e = nums[left];
        // lt记录小于临界点的下标
        int lt = left;
        // 考察元素的下标
        int i = left + 1;
        // gt 记录大于临界点的下标
        int gt = right + 1;
        while (i < gt) {
            // 当前考察元素小于临界点
            if (nums[i] < e) {
                // 交换位置
                swap(nums, i, lt + 1);
                // lt维护下标
                lt ++;
                // 对应的考察元素向后挪一位
                i ++;
            } else if (nums[i] > e) {  // 如果大于
                // 把右端元素和左端进行交换
                swap(nums, gt - 1, i);
                // 对应的gt下标向前挪一位
                gt --;
            } else {
                // 这里就是相等情况,只要i 向后挪一位即可。解决重复元素排序问题
                i ++;
            }
        }
        swap(nums, left, lt);
        sort(nums, left, lt - 1);
        sort(nums, gt, right);
    }



    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

}


上面就是三路排序的算法实现,可以看到,首先就是临界点的取值,我们采取了随机方式,避免快排退化成O(n^2),然后我们把整个待排序数组分成了三部门进行我们的算法实现。这样子我们比较完整的快排其实已经出来了,这样子的快排就是比较优雅的了。


三、尾语

其实这篇快排写的还是比较简单,不过主要的逻辑都集中在代码上。数据结构和算法永远是程序的灵魂、核心。 所以,应该要掌握这些核心,才能让自己不被淘汰!加油!