跳转至

Java 快速排序全解析

简介

快速排序(Quick Sort)是一种高效的排序算法,由英国计算机科学家 Tony Hoare 在 1959 年提出。它采用分治法(Divide and Conquer)的策略,平均时间复杂度为 $O(n log n)$,在大多数情况下表现出色,因此在实际应用中被广泛使用。本文将围绕 Java 实现的快速排序展开,详细介绍其基础概念、使用方法、常见实践以及最佳实践。

目录

  1. 快速排序基础概念
  2. Java 实现快速排序的使用方法
  3. 常见实践
  4. 最佳实践
  5. 小结
  6. 参考资料

快速排序基础概念

分治法思想

快速排序运用了分治法的思想,其基本步骤如下: 1. 分解:选择一个基准元素(pivot),将数组分为两部分,使得左边部分的所有元素都小于等于基准元素,右边部分的所有元素都大于等于基准元素。 2. 解决:递归地对左右两部分分别进行快速排序。 3. 合并:由于左右两部分已经有序,整个数组自然也就有序了,无需额外的合并操作。

算法复杂度

  • 时间复杂度:平均情况下为 $O(n log n)$,最坏情况下为 $O(n^2)$。最坏情况发生在每次选择的基准元素都是数组中的最大或最小元素时。
  • 空间复杂度:平均情况下为 $O(log n)$,主要用于递归调用栈的空间。

Java 实现快速排序的使用方法

以下是一个简单的 Java 实现快速排序的代码示例:

public class QuickSort {
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            // 分区操作,获取基准元素的最终位置
            int pivotIndex = partition(arr, low, high);
            // 递归排序左半部分
            quickSort(arr, low, pivotIndex - 1);
            // 递归排序右半部分
            quickSort(arr, pivotIndex + 1, high);
        }
    }

    private static int partition(int[] arr, int low, int high) {
        // 选择最后一个元素作为基准元素
        int pivot = arr[high];
        int i = low - 1;
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                // 交换 arr[i] 和 arr[j]
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        // 将基准元素放到正确的位置
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;
        return i + 1;
    }

    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        quickSort(arr, 0, arr.length - 1);
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}

代码解释

  1. quickSort 方法:这是快速排序的主方法,它接收一个整数数组 arr 以及数组的起始索引 low 和结束索引 high。如果 low 小于 high,则进行分区操作,并递归地对左右两部分进行排序。
  2. partition 方法:该方法用于分区操作,选择最后一个元素作为基准元素,将小于等于基准元素的元素移到左边,大于基准元素的元素移到右边,最后返回基准元素的最终位置。
  3. main 方法:创建一个测试数组,调用 quickSort 方法进行排序,并输出排序后的数组。

常见实践

随机选择基准元素

为了避免最坏情况的发生,可以随机选择基准元素。以下是修改后的代码:

import java.util.Random;

public class QuickSortRandomPivot {
    private static Random random = new Random();

    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            // 随机选择基准元素
            int pivotIndex = randomPartition(arr, low, high);
            quickSort(arr, low, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, high);
        }
    }

    private static int randomPartition(int[] arr, int low, int high) {
        // 随机选择一个索引作为基准元素的索引
        int randomIndex = low + random.nextInt(high - low + 1);
        // 将随机选择的基准元素放到最后
        int temp = arr[randomIndex];
        arr[randomIndex] = arr[high];
        arr[high] = temp;
        return partition(arr, low, high);
    }

    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;
        return i + 1;
    }

    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        quickSort(arr, 0, arr.length - 1);
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}

小数组使用插入排序

对于小规模的数组,插入排序的性能可能更好。因此,可以在数组长度小于某个阈值时使用插入排序。以下是修改后的代码:

public class QuickSortInsertion {
    private static final int THRESHOLD = 10;

    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            if (high - low + 1 <= THRESHOLD) {
                // 小数组使用插入排序
                insertionSort(arr, low, high);
            } else {
                int pivotIndex = partition(arr, low, high);
                quickSort(arr, low, pivotIndex - 1);
                quickSort(arr, pivotIndex + 1, high);
            }
        }
    }

    private static void insertionSort(int[] arr, int low, int high) {
        for (int i = low + 1; i <= high; i++) {
            int key = arr[i];
            int j = i - 1;
            while (j >= low && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }

    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;
        return i + 1;
    }

    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        quickSort(arr, 0, arr.length - 1);
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}

最佳实践

三数取中法选择基准元素

三数取中法是一种常用的选择基准元素的方法,它选择数组的第一个元素、中间元素和最后一个元素中的中位数作为基准元素。以下是实现代码:

public class QuickSortMedianOfThree {
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            // 三数取中选择基准元素
            int pivotIndex = medianOfThreePartition(arr, low, high);
            quickSort(arr, low, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, high);
        }
    }

    private static int medianOfThreePartition(int[] arr, int low, int high) {
        int mid = low + (high - low) / 2;
        // 三数取中
        if (arr[mid] < arr[low]) {
            swap(arr, low, mid);
        }
        if (arr[high] < arr[low]) {
            swap(arr, low, high);
        }
        if (arr[mid] < arr[high]) {
            swap(arr, mid, high);
        }
        return partition(arr, low, high);
    }

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

    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, high);
        return i + 1;
    }

    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        quickSort(arr, 0, arr.length - 1);
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}

小结

快速排序是一种高效的排序算法,在大多数情况下具有较好的性能。通过随机选择基准元素、小数组使用插入排序以及三数取中法选择基准元素等优化方法,可以进一步提高快速排序的性能,避免最坏情况的发生。在实际应用中,可以根据具体情况选择合适的优化策略。

参考资料

  1. 《算法导论》
  2. 维基百科 - 快速排序
  3. GeeksforGeeks - QuickSort