【经典排序算法】6. 快速排序

快速排序是冒泡排序的一种改进版本。

快速排序虽然最坏情况的时间复杂度是O(n²)(给顺序数组排列,将退化为冒泡排序),但是其性能在顺序性越差的数据中表现越好,甚至可以比归并排序要好。这是因为虽然快速排序跟归并排序的平均时间复杂度都是O(nlogn),但是快速排序的O(nlogn) 记号中隐含的常数因子很小。

代码如下:

public class Main {

    public static void main(String[] args) {
        int[] arr = {3, 3, 5, 6, 2, 1};
        System.out.print("排序前:");
        arrPrint(arr);
        QuickSort(arr);
        System.out.print("排序后:");
        arrPrint(arr);
    }

    // 快速排序
    // 快速排序和归并排序一样采用了分治法的设计思想。
    // 把大问题分解成小问题,把大数组分解成小数组。
    //
    // 调用快速排序的递归函数,左索引记为left,初始化为0,
    // 右索引记为right,初始化为arr.length - 1。
    private static void QuickSort(int[] arr) {
        quickSort(arr, 0, arr.length - 1);
    }

    // 快速排序的递归函数
    // 递归终止条件为左索引>=右索引。不满足终止条件时:
    // 调用基准值分割函数partition,得到分割后的索引mid,
    // mid-1即为分割数组后的左子数组的终点,mid+1即为分割数组后右子数组的起点
    // 递归调用快速排序quickSort,对左子数组进行快速排序,
    // 递归调用快速排序quickSort,对右子数组进行快速排序。
    //
    // 想看中间输出的可以在partition函数后面使用arrPrint(arr)来打印
    private static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = partition(arr, left, right);
            quickSort(arr, left, mid - 1);
            quickSort(arr, mid + 1, right);
        }
    }

    // 基准值分割函数partition
    // 选取基准值pivot后,循环使用双指针寻找左边比pivot大的数arr[l],
    // 右边比pivot小的数arr[r],并交换arr[l] arr[r]的位置。
    // 使得在r和l相遇位置的左半边的数不大于pivot,而在右半边的数则不小于pivot,
    // 最后把pivot交换到r和l的相遇位置,即可补全空间意义上真正的基准值分割。
    // 此时pivot的位置(r和l的相遇位置)将数组分割为了两边。左半边总是不大于右半边的数字。
    // 之后再利用分治法递归地调用partition,继续基准值分割左右子数组即可完成整个快排。
    //
    // 选取基准值pivot(默认arr是随机排序,所以直接取arr[left]),
    // 将左指针初始化为left + 1,右指针初始化为right,
    // 满足l小于r时(双指针没有超过遍历边界时)执行第一层while循环:
    // 第2层第1个while: 如果左指针l没有超过边界,且遍历元素arr[l]不大于pivot,
    //                  则左指针l循环右移,直到找到从左往右第一个比pivot大的遍历数arr[l]。
    // 第2层第1个while:同理,如果右指针r没有超边界,且遍历元素arr[r]不小于pivot,
    //                  则右指针r循环左移,直到找到从右往左第一个比pivot大的遍历数arr[r]。
    // 如果此时l依然满足小于r(双指针没有过界),则将arr[l] arr[r]交换位置,
    // l和r相遇后,所有while结束,此时将pivot交换到r与l的相遇位置。
    // pivot记录了arr[left]的值,所以先用arr[r]把arr[left]覆盖掉,
    // 再把pivot放到arr[r]上,完成最后交换,补全空间意义上真正的基准值分割,
    // 此时pivot的位置(r和l的相遇位置)将数组分割为了两边,左半边总是不大于右半边的数字。
    private static int partition(int[] arr, int left, int right) {
        int pivot = arr[left];
        int l = left;
        int r = right;
        while (l < r) {
            while (l <= r && arr[l] <= pivot)
                l++;
            while (l <= r && arr[r] >= pivot)
                r--;
            if (l < r)
                swap(arr, l, r);
        }
        arr[left] = arr[r];
        arr[r] = pivot;
        return r;
    }

    // partition中的交换元素位置函数
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 辅助函数:将int[] 打印出来
    private static void arrPrint(int[] arr) {
        StringBuilder str = new StringBuilder();
        str.append("[");
        for (int v : arr) {
            str.append(v + ", ");
        }
        str.delete(str.length() - 2, str.length());
        str.append("]");
        System.out.println(str.toString());
    }
}

该实例的快速排序动画演示如下:(省略了基准值分割函数partition)
在这里插入图片描述

基准值分割函数动画演示如下(动图有误,l应该从left开始):

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/fisherish/article/details/113843498