在 C 语言中实现快速排序(Quick Sort)有多种方式。我们可以通过不同的 划分策略 或 递归处理方式 实现不同版本的快速排序。下面将展示三种常见版本的快速排序实现:

快速排序基本原理

快速排序是基于分治法的排序算法。基本步骤如下:

  1. 选择基准:从待排序的数组中选择一个元素作为基准(pivot)。
  2. 划分操作:将数组分为两部分,一部分小于基准,另一部分大于基准。
  3. 递归排序:递归地对两个子数组进行快速排序,直到子数组的大小为 1 或 0。

三种版本的快速排序实现

我们将实现三种版本的快速排序:

  1. 经典版本:选择基准元素为第一个元素。
  2. 随机基准版本:选择基准元素为数组中的随机元素。
  3. 三路划分版本:针对重复元素较多的情况,使用三路划分方法(荷兰国旗问题)。

1. 经典快速排序

经典的快速排序算法选择数组的第一个元素作为基准,并通过 一次划分 将数组分为两个部分。

代码实现:

#include <stdio.h>

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int partition(int arr[], int low, int high) {
    int pivot = arr[low];  // 选择第一个元素为基准
    int left = low + 1;
    int right = high;

    while (left <= right) {
        // 左边找到大于或等于基准的元素
        while (left <= right && arr[left] <= pivot) left++;
        // 右边找到小于或等于基准的元素
        while (left <= right && arr[right] > pivot) right--;

        if (left < right) {
            swap(&arr[left], &arr[right]);
        }
    }

    // 将基准元素放到正确的位置
    swap(&arr[low], &arr[right]);
    return right;
}

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);
    }
}

int main() {
    int arr[] = {10, 80, 30, 90, 40, 50, 70};
    int n = sizeof(arr) / sizeof(arr[0]);

    quickSort(arr, 0, n - 1);

    printf("Sorted array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

关键点

  • 选择基准:选择第一个元素作为基准。
  • 划分过程:将数组分为两部分,一部分小于基准,另一部分大于基准。

2. 随机基准版本

在经典快速排序中,选择第一个元素作为基准可能导致最坏的时间复杂度 O(n^2),特别是当输入数据已经接近有序时。为此,可以通过 随机选择基准元素 来提高算法的性能,避免最坏情况。

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int partition(int arr[], int low, int high) {
    // 随机选择基准
    int pivotIndex = low + rand() % (high - low + 1);
    swap(&arr[low], &arr[pivotIndex]);  // 将基准放到第一个位置
    int pivot = arr[low];
    int left = low + 1;
    int right = high;

    while (left <= right) {
        while (left <= right && arr[left] <= pivot) left++;
        while (left <= right && arr[right] > pivot) right--;

        if (left < right) {
            swap(&arr[left], &arr[right]);
        }
    }

    swap(&arr[low], &arr[right]);
    return right;
}

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);
    }
}

int main() {
    int arr[] = {10, 80, 30, 90, 40, 50, 70};
    int n = sizeof(arr) / sizeof(arr[0]);

    // 初始化随机种子
    srand(time(NULL));

    quickSort(arr, 0, n - 1);

    printf("Sorted array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

关键点

  • 随机基准:通过 rand() 函数随机选择一个基准,避免了最坏情况的出现。

3. 三路划分版本(荷兰国旗问题)

在快速排序中,如果数组中存在大量重复元素,经典的快速排序方法可能会导致性能下降。三路划分版本的快速排序可以处理这种情况,它通过将数组划分为三个部分:小于基准、等于基准、大于基准,从而减少了重复元素的处理。

代码实现:

#include <stdio.h>

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void threeWayPartition(int arr[], int low, int high, int *lt, int *gt) {
    int pivot = arr[low];
    int i = low + 1;
    int j = high;
    *lt = low;
    *gt = high;

    while (i <= *gt) {
        if (arr[i] < pivot) {
            swap(&arr[i], &arr[*lt]);
            (*lt)++;
            i++;
        } else if (arr[i] > pivot) {
            swap(&arr[i], &arr[*gt]);
            (*gt)--;
        } else {
            i++;
        }
    }
}

void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int lt, gt;
        threeWayPartition(arr, low, high, &lt, &gt);
        quickSort(arr, low, lt - 1);
        quickSort(arr, gt + 1, high);
    }
}

int main() {
    int arr[] = {10, 80, 30, 90, 40, 50, 70, 40, 40};
    int n = sizeof(arr) / sizeof(arr[0]);

    quickSort(arr, 0, n - 1);

    printf("Sorted array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

关键点

  • 三路划分:根据与基准元素的关系,将数组划分为三部分:小于基准、等于基准、大于基准,能够有效处理重复元素。
  • 优化:该方法特别适合包含大量重复元素的数组。

总结

  • 经典快速排序:使用数组的第一个元素作为基准,适合大多数情况。
  • 随机基准快速排序:通过随机选择基准来避免最坏情况,增加算法的平均性能。
  • 三路划分快速排序:通过三路划分的方式处理重复元素,避免了性能下降,适用于有大量重复元素的数组。

这些版本的快速排序可以根据具体的场景进行选择,以提高排序效率。