数组排序02|快速排序:排序算法中的闪电

数组排序02|快速排序:排序算法中的闪电

快速排序是一种高效的排序算法,由英国计算机科学家托尼·霍尔在1960年提出。它的核心思想也是“分而治之”,通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程递归进行,以此达到整个数据变成有序序列。

快速排序的详细步骤

  1. 选择基准值:从数组中选择一个基准值(pivot)。
  2. 分区操作:将数组中小于基准值的元素放在基准值的左边,大于基准值的元素放在基准值的右边,基准值放在中间。
  3. 递归排序:对基准值左右两边的子数组按照同样的方式进行递归排序。

实现代码

分区操作是快速排序的核心,下面是分区操作的实现代码:

// Lomuto partition scheme
int partition(int arr[], int low, int high) {
    // 选择数组最后一个元素作为枢轴
    int pivot = arr[high];
    // i 是小于枢轴元素的最后一个元素的索引
    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;
}

实现了分区操作后,我们可以使用递归的方式实现快速排序:

void quick_sort(int arr[], int low, int high) {
    if (low < high) {
        // 分区操作,将数组分为两部分
        int mid = partition(arr, low, high);
        // 递归排序左边的子数组
        quick_sort(arr, low, mid - 1);
        // 递归排序右边的子数组
        quick_sort(arr, mid + 1, high);
    }
}

算法分析

  • 时间复杂度:平均情况下为 O(nlogn),最坏情况下为 O(n^2)
  • 空间复杂度:O(logn)

快速排序的优化方案

1. Hoare 分区方案

Lomuto 分区方案在最坏情况下的时间复杂度为 O(n^2),主要原因是它会将所有小于枢轴的元素放在一边,而将所有大于或等于枢轴的元素放在另一边。更优的分区方案是 Hoare 分区方案,它在处理大量重复元素时性能更好。Hoare 分区方案通过两个指针从数组的两端向中间移动,交换不符合条件的元素,直到两个指针相遇。

下面是 Hoare 分区方案的实现代码:

// Hoare partition scheme
int partition(int arr[], int low, int high) {
    // 选择数组中间的元素作为枢轴
    int pivot = arr[(low + high) / 2];
    int i = low - 1, j = high + 1;
    // 交换元素直到 i >= j
    while (1) {
        do {
            i++;
        } while (arr[i] < pivot);
        do {
            j--;
        } while (arr[j] > pivot);
        if (i >= j) {
            return j;
        }
        // 交换 arr[i] 和 arr[j]
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2. 三数取中法

快速排序的性能和枢轴的选择有很大关系,如果选择的枢轴是数组的最大值或最小值,会导致快速排序的性能下降。为了避免这种情况,可以采用三数取中法,即选择数组的第一个元素、中间元素和最后一个元素的中位数作为枢轴。

下面是三数取中法的实现代码:

// 三数取中法选择枢轴
int median_of_three(int arr[], int low, int high) {
    int mid = (low + high) / 2;
    // 保证 arr[low] <= arr[mid] <= arr[high]
    if (arr[low] > arr[mid]) {
        int temp = arr[low];
        arr[low] = arr[mid];
        arr[mid] = temp;
    }
    if (arr[mid] > arr[high]) {
        int temp = arr[mid];
        arr[mid] = arr[high];
        arr[high] = temp;
    }
    if (arr[low] > arr[mid]) {
        int temp = arr[low];
        arr[low] = arr[mid];
        arr[mid] = temp;
    }
    return mid;
}

使用三数取中法选择枢轴后,新的分区函数代码如下:

int partition(int arr[], int low, int high) {
    // 使用三数取中法选择枢轴
    int pivot = arr[median_of_three(arr, low, high)];
    int i = low - 1, j = high + 1;
    // 交换元素直到 i >= j
    while (1) {
        do {
            i++;
        } while (arr[i] < pivot);
        do {
            j--;
        } while (arr[j] > pivot);
        if (i >= j) {
            return j;
        }
        // 交换 arr[i] 和 arr[j]
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

3. 随机化快速排序

除了三数取中法,还可以通过随机选择枢轴的方式来优化快速排序。随机化快速排序的思路是在每次递归调用中,随机选择一个枢轴,而不是固定地选择第一个元素、最后一个元素或中间元素。

下面是随机化快速排序的实现代码:

int partition(int arr[], int low, int high) {
    // 随机选择枢轴
    int pivot_index = rand() % (high - low + 1) + low;
    int pivot = arr[pivot_index];
    // 交换 arr[pivot_index] 和 arr[high]
    int temp = arr[pivot_index];
    arr[pivot_index] = arr[high];
    arr[high] = temp;
    int i = low - 1, j = high + 1;
    // 交换元素直到 i >= j
    while (1) {
        do {
            i++;
        } while (arr[i] < pivot);
        do {
            j--;
        } while (arr[j] > pivot);
        if (i >= j) {
            return j;
        }
        // 交换 arr[i] 和 arr[j]
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

4. 双枢轴快速排序(Dual-Pivot Quick Sort)

双枢轴快速排序是一种改进的快速排序算法,也被称为“双路快排”,由俄罗斯计算机科学家弗拉基米尔·约瑟福维奇·沃洛佐金在2009年提出。双枢轴快速排序通过两个枢轴将数组分为三部分,左边的部分小于第一个枢轴,中间的部分在第一个枢轴和第二个枢轴之间,右边的部分大于第二个枢轴,也就是说,双枢轴快速排序将数组分为三部分,而不是两部分。

下面是双枢轴快速排序的实现代码:

void dual_pivot_quick_sort(int arr[], int low, int high) {
    if (low < high) {
        // 选择两个枢轴
        int pivot1 = arr[low], pivot2 = arr[high];
        if (pivot1 > pivot2) {
            int temp = pivot1;
            pivot1 = pivot2;
            pivot2 = temp;
        }
        int i = low + 1, k = low + 1, j = high - 1;
        while (k <= j) {
            if (arr[k] < pivot1) {
                // 交换 arr[i] 和 arr[k]
                int temp = arr[i];
                arr[i] = arr[k];
                arr[k] = temp;
                i++;
            } else if (arr[k] >= pivot2) {
                while (arr[j] > pivot2 && k < j) {
                    j--;
                }
                // 交换 arr[j] 和 arr[k]
                int temp = arr[j];
                arr[j] = arr[k];
                arr[k] = temp;
                j--;
                if (arr[k] < pivot1) {
                    // 交换 arr[i] 和 arr[k]
                    int temp = arr[i];
                    arr[i] = arr[k];
                    arr[k] = temp;
                    i++;
                }
            }
            k++;
        }
        i--;
        j++;
        // 交换 arr[low] 和 arr[i]
        int temp = arr[low];
        arr[low] = arr[i];
        arr[i] = temp;
        // 交换 arr[high] 和 arr[j]
        temp = arr[high];
        arr[high] = arr[j];
        arr[j] = temp;
        // 递归排序左边的子数组
        dual_pivot_quick_sort(arr, low, i - 1);
        // 递归排序中间的子数组
        dual_pivot_quick_sort(arr, i + 1, j - 1);
        // 递归排序右边的子数组
        dual_pivot_quick_sort(arr, j + 1, high);
    }
}

总结

相比于归并排序,快速排序是一种原地排序算法,不需要额外的空间,但在最坏情况下的时间复杂度为 O(n^2)。通过优化枢轴的选择和分区方案,可以提高快速排序的性能,使其在大多数情况下表现优异,因此快速排序是目前最常用的排序算法之一。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值