快速排序笔记

快速排序算法

一、概述

快速排序(Quicksort)是一种常用的高效排序算法,采用分治法的思想。其核心思想是通过选择一个基准元素,将数组划分为两部分,使得基准左边的元素都小于基准,右边的元素都大于基准,然后递归地对左右部分继续进行排序。

特点

  • 时间复杂度
    • 平均时间复杂度:O(n log n)
    • 最坏时间复杂度:O(n²)(当每次选的基准值是最大或最小时)
  • 空间复杂度:O(log n) (递归栈的空间开销)
  • 不稳定:快速排序不是一种稳定的排序算法。

二、算法步骤

  1. 选择基准元素。
  2. 通过一趟排序将待排数组分为两部分,一部分比基准小,另一部分比基准大。
  3. 递归地对两部分进行快速排序。

三、代码模板(Java版)

import java.util.Scanner;

public class QuickSort {
    private static final int N = 1000000 + 10;
    private static int[] q = new int[N];
    private static int n;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        for (int i = 0; i < n; i++) {
            q[i] = scanner.nextInt();
        }
        quickSort(q, 0, n - 1);
        for (int i = 0; i < n; i++) {
            System.out.print(q[i] + " ");
        }
        scanner.close();
    }
    public static void quickSort(int[] arr, int l, int r) {
        if (l < r) {
            int i = l - 1;
            int j = r + 1;
            int x = arr[l];
            while (i < j) {
                do {
                    i++;
                } while (i < r && arr[i] < x);
                do {
                    j--;
                } while (arr[j] > x);
                if (i < j) {
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
            quickSort(arr, l, j);
            quickSort(arr, j + 1, r);
        }
    }

}

四、代码讲解

  1. quickSort 方法:这是快速排序的主方法,递归地对数组进行分区和排序。
  2. partition 方法:分区操作,选择一个基准元素,并将数组划分为两部分。
  3. swap 方法:简单的元素交换操作,用于在分区过程中交换数组中的元素。

注意事项

  • 递归终止条件:当 low >= high 时,递归结束。
  • 基准选择:这里选择了数组的最后一个元素作为基准,也可以根据不同情况优化基准的选择,如随机选择基准或使用三数取中法。

五、补充

在某些快速排序的实现中,int i = l - 1; int j = r + 1; 这种初始化方式出现在特定的双指针(或左右指针)分区法中。我们来看一下它背后的逻辑和原理。

1、快速排序的分区操作

在快速排序中,分区操作的目的是将数组分成两部分:

  • 一部分的元素小于基准值(pivot),
  • 另一部分的元素大于基准值。

双指针分区法 是通过从数组的两端同时向中间移动指针来实现这个分区过程。

2、i = l - 1j = r + 1 的作用

  1. i = l - 1;

    • 这个指针 i 最初指向左边界 l 的前一个位置。这样做的原因是,当我们开始移动指针时,i 会先增加(i++),所以最初将其设置为 l - 1,可以确保一开始从左边界的第一个元素(即 arr[l])开始比较。
    • 如果直接初始化为 l,那么第一次增量操作时会跳过 arr[l],导致错误的比较顺序。
  2. j = r + 1;

    • 这个指针 j 最初指向右边界 r 的后一个位置。因为我们在分区过程中,指针 j 会向左移动(j--),将其初始设置为 r + 1 确保可以从右边界的第一个元素(即 arr[r])开始比较。
    • 如果直接初始化为 r,第一次减量操作时会跳过 arr[r],导致同样的逻辑错误。

3、分区过程的具体操作

使用双指针的分区方法时,整个过程如下:

  1. 从数组的两端开始,i 从左往右扫描,j 从右往左扫描。
  2. i 找到一个大于等于基准值的元素时,它停止向右移动。
  3. j 找到一个小于等于基准值的元素时,它停止向左移动。
  4. 这时,ij 的元素满足互换条件,将它们交换位置。
  5. 继续上述步骤,直到 ij 相遇或交错,这时分区结束。

4、代码示例

这种逻辑可以用以下代码片段展示:

public class QuickSort {
    public static void quickSort(int[] arr, int l, int r) {
        if (l >= r) return;
        int pivot = arr[(l + r) / 2];  // 选取中间的元素作为基准
        int i = l - 1, j = r + 1;

        while (i < j) {
            // 从左到右找到一个大于或等于 pivot 的元素
            do i++; while (arr[i] < pivot);
            // 从右到左找到一个小于或等于 pivot 的元素
            do j--; while (arr[j] > pivot);
            // 如果 i 和 j 还没有相遇,交换这两个元素
            if (i < j) swap(arr, i, j);
        }

        // 递归地对左边和右边进行排序
        quickSort(arr, l, j);
        quickSort(arr, j + 1, r);
    }

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

5、总结

int i = l - 1;int j = r + 1; 这种初始化方式的主要目的是为了确保在分区过程中,指针从左右边界的元素开始遍历时能够正常工作。在第一次移动指针之前,它们不会跳过边界上的元素

  • 17
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值