C语言快速排序数据结构如何高效实现?

99ANYc3cd6
预计阅读时长 17 分钟
位置: 首页 C语言 正文

快速排序是一种非常高效的排序算法,其核心思想是 “分治法” (Divide and Conquer)

快速排序的核心思想

  1. 选择基准:从待排序的数组中选择一个元素,称为 “基准”(Pivot)。
  2. 分区:重新排列数组,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆放在基准后面,在这个分区结束之后,基准就处于数组的中间位置,这个操作称为 分区 操作。
  3. 递归排序:递归地对基准左边和右边的子数组进行快速排序。

就是每次排序都能把一个元素放到它最终应该在的位置上,然后把剩下的数组分成两半,继续这个过程。


C 语言实现

我们将实现两个核心函数:

  • partition(): 分区函数,负责将数组分成两部分。
  • quickSort(): 递归排序函数。

1 分区函数

分区是快速排序的灵魂,我们通常使用 “双指针法” 来实现分区。

算法步骤:

  1. 选择最右边的元素作为 pivot(基准)。
  2. 初始化一个指针 i,指向数组的起始位置 -1,这个指针 i 的作用是记录最后一个小于 pivot 的元素的位置。
  3. 使用另一个指针 j 从左到右遍历数组(从 0pivot 的前一个位置)。
  4. arr[j] 小于或等于 pivot,就将 i 向右移动一位,然后交换 arr[i]arr[j]
  5. 当遍历结束后,pivot 的正确位置就在 i+1,将 pivot 放到这个位置,并返回这个位置的索引。

代码实现:

// 分区函数
// 返回基准值的最终位置
int partition(int arr[], int low, int high) {
    // 选择最右边的元素作为基准
    int pivot = arr[high];
    // i 指向小于基准的最后一个元素的位置
    int i = (low - 1); 
    for (int j = low; j <= high - 1; j++) {
        // 如果当前元素小于或等于基准
        if (arr[j] <= pivot) {
            // i 向右移动
            i++; 
            // 交换 arr[i] 和 arr[j]
            swap(&arr[i], &arr[j]);
        }
    }
    // 将基准值放到正确的位置上 (i+1)
    swap(&arr[i + 1], &arr[high]);
    // 返回基准值的索引
    return (i + 1);
}

辅助交换函数:

// 交换两个整数的值
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

2 递归排序函数

这个函数相对简单,它利用分区函数找到基准的位置,然后递归地对左右两部分进行排序。

代码实现:

// 快速排序主函数 (递归)
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        // pi 是分区后基准的索引
        int pi = partition(arr, low, high);
        // 递归排序基准左边的子数组
        quickSort(arr, low, pi - 1);
        // 递归排序基准右边的子数组
        quickSort(arr, pi + 1, high);
    }
}

完整代码示例

下面是一个完整的、可运行的 C 语言程序,包含了 main 函数来测试我们的快速排序实现。

#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[high]; // 选择最右边的元素作为基准
    int i = (low - 1);     // 小于基准的元素的边界
    for (int j = low; j <= high - 1; j++) {
        // 如果当前元素小于或等于基准
        if (arr[j] <= pivot) {
            i++; // 扩小于基准的区域
            swap(&arr[i], &arr[j]);
        }
    }
    // 将基准放到正确的位置
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}
// 快速排序主函数 (递归)
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high); // pi 是分区点的索引
        // 递归排序左半部分和右半部分
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}
// 打印数组的函数
void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
// 主函数,用于测试
int main() {
    int arr[] = {10, 7, 8, 9, 1, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("原始数组: \n");
    printArray(arr, n);
    quickSort(arr, 0, n - 1);
    printf("排序后的数组: \n");
    printArray(arr, n);
    return 0;
}

编译和运行:

gcc quicksort.c -o quicksort
./quicksort

输出:

原始数组: 
10 7 8 9 1 5 
排序后的数组: 
1 5 7 8 9 10 

算法分析

  • 时间复杂度:

    • 平均情况: O(n log n),这是快速排序最常见的情况,每次分区都能将数组大致分成两半。
    • 最好情况: O(n log n),当每次分区都能将数组完美地分成两个大小相等的子数组时。
    • 最坏情况: O(n²),当数组已经有序(正序或逆序),并且我们总是选择第一个或最后一个元素作为基准时,每次分区只能减少一个元素,导致递归深度为 n。
  • 空间复杂度:

    O(log n),主要来自于递归调用栈的深度,在平均情况下,递归深度为 log n,在最坏情况下,空间复杂度会退化到 O(n)。

  • 稳定性:

    • 不稳定,在分区过程中,相等的元素的相对位置可能会发生改变。

优化和变种

1 基准的选择优化

最坏情况 O(n²) 的发生很大程度上取决于基准的选择,为了避免总是选择到最差(如最大或最小)的元素,可以采用以下策略:

  1. 随机化基准: 在分区前,随机选择一个元素与 arr[high] 交换,这使得算法对于任何输入数据的性能都趋向于平均情况。

    // 在 quickSort 函数中,partition 调用之前加入
    int random = low + rand() % (high - low);
    swap(&arr[random], &arr[high]);
  2. 三数取中法: 选择 arr[low], arr[mid], arr[high] 三个元素的中位数作为基准,这能有效避免在已排序或近似排序的数组上出现最坏情况。

2 小数组优化

对于非常小的子数组(长度小于 10),快速排序的递归开销可能比插入排序还要大,一个常见的优化是:当子数组长度小于某个阈值(如 10)时,改用插入排序。

// 在 quickSort 函数中
void quickSort(int arr[], int low, int high) {
    // 当子数组长度小于 10 时,使用插入排序
    if (high - low < 10) {
        insertionSort(arr, low, high);
        return;
    }
    // ... 其余代码不变
}

3 尾递归优化

标准的快速排序有两个递归调用,这可能导致栈空间深度达到 O(n),可以通过将其中一个递归调用改为循环来优化,将递归深度限制在 O(log n)。

// 尾递归优化版本的 quickSort
void quickSort(int arr[], int low, int high) {
    while (low < high) {
        int pi = partition(arr, low, high);
        // 先处理较小的子数组,以减少栈深度
        if (pi - low < high - pi) {
            quickSort(arr, low, pi - 1);
            low = pi + 1;
        } else {
            quickSort(arr, pi + 1, high);
            high = pi - 1;
        }
    }
}

这些优化在实际应用中非常重要,可以使快速排序的性能更加稳定和高效。

-- 展开阅读全文 --
头像
织梦模板下载方法是什么?
« 上一篇 今天
dede如何获取当前栏目或文章的ID?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]