c++快速排序

简介:

基本思想

快速排序采用的是分治思想,即在一个无序的序列中选取一个任意的基准元素pivot,利用pivot将待排序的序列分成两部分,前面部分元素均小于或等于基准元素,后面部分均大于或等于基准元素,然后采用递归的方法分别对前后两部分重复上述操作,直到将无序序列排列成有序序列。 

  • 发明者:Tony Hoare 在 1959 年提出了快速排序算法,最初是为了解决 ALGOL 编程语言中的排序问题。
  • 算法思想:快速排序的核心思想是通过分区(Partition)将数组分成两部分,一部分小于基准值(pivot),另一部分大于基准值,然后递归地对这两部分进行排序。
  • 性能
    • 平均时间复杂度:O(n log n)。
    • 最坏时间复杂度:O(n²)(当每次选择的基准值都导致不平衡分区时)。
    • 空间复杂度:O(log n)(递归栈的深度)。
  • 应用场景:快速排序广泛应用于各种编程语言的标准库中(如 C++ 的 std::sort),因为它在大规模数据排序中表现优异。
快速排序的缺点

尽管快速排序非常高效,但它也有一些缺点:

  • 最坏情况时间复杂度:在最坏情况下(例如,当数组已经有序或所有元素相等时),快速排序的时间复杂度会退化为 O(n²)。这通常发生在基准值选择不当时。
  • 递归深度:快速排序是递归实现的,如果递归深度过大,可能会导致栈溢出问题。
  • 不稳定:快速排序是一种不稳定的排序算法,这意味着相同元素的相对位置可能会在排序过程中发生变化。
  • 基准值选择:基准值的选择对算法性能影响很大。如果基准值选择不当,可能会导致排序效率下降。
优化方法

为了克服快速排序的缺点,可以采取以下优化措施:

  1. 基准值选择优化

    • 随机选择基准值:随机选择一个元素作为基准值,可以减少最坏情况的发生概率。
    • 三数取中法:选择数组的第一个、中间和最后一个元素的中位数作为基准值,可以有效避免最坏情况。
  2. 小数组优化

    • 对于小数组(例如,元素数量少于 10 个),可以切换到插入排序,因为插入排序在小规模数据上表现更好。
  3. 尾递归优化

    • 通过尾递归优化,可以减少递归调用的深度,避免栈溢出问题。

排序步骤

原理

设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它左边,所有比它大的数都放到它右边,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。 

一趟快速排序的算法是: 

(1)设置两个变量i、j,排序开始的时候:i=0,j=N-1; 

(2)以第一个数组元素作为关键数据,赋值给key,即key=A[0]; 

(3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]的值交换; 

(4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换; 

(5)重复第3、4步,直到i==j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。 

排序演示

假设一开始序列{xi}是:5,3,7,6,4,1,0,2,9,10,8。

此时,ref=5,i=1,j=11,从后往前找,第一个比5小的数是x8=2,因此序列为:2,3,7,6,4,1,0,5,9,10,8。

此时i=1,j=8,从前往后找,第一个比5大的数是x3=7,因此序列为:2,3,5,6,4,1,0,7,9,10,8。

此时,i=3,j=8,从第8位往前找,第一个比5小的数是x7=0,因此:2,3,0,6,4,1,5,7,9,10,8。

此时,i=3,j=7,从第3位往后找,第一个比5大的数是x4=6,因此:2,3,0,5,4,1,6,7,9,10,8。

此时,i=4,j=7,从第7位往前找,第一个比5小的数是x6=1,因此:2,3,0,1,4,5,6,7,9,10,8。

此时,i=4,j=6,从第4位往后找,直到第6位才有比5大的数,这时,i=j=6,ref成为一条分界线,它之前的数都比它小,之后的数都比它大,对于前后两部分数,可以采用同样的方法来排序。 

示例:

#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::swap

// 分区函数
int partition(std::vector<int>& arr, int low, int high) {
    // 三数取中法选择基准值
    int mid = low + (high - low) / 2;
    if (arr[low] > arr[mid]) std::swap(arr[low], arr[mid]);
    if (arr[low] > arr[high]) std::swap(arr[low], arr[high]);
    if (arr[mid] > arr[high]) std::swap(arr[mid], arr[high]);
    int pivot = arr[mid]; // 选择中位数作为基准值

    std::swap(arr[mid], arr[high]); // 将基准值放到最后
    int i = low - 1; // i 是小于基准值的最后一个元素的索引

    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            std::swap(arr[i], arr[j]); // 将小于基准值的元素交换到前面
        }
    }
    std::swap(arr[i + 1], arr[high]); // 将基准值放到正确的位置
    return i + 1; // 返回基准值的索引
}

// 快速排序函数
void quickSort(std::vector<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;
        }
    }
}

int main() {
    std::vector<int> arr = {10, 7, 8, 9, 1, 5};
    int n = arr.size();

    std::cout << "Original array: ";
    for (int i : arr) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    quickSort(arr, 0, n - 1);

    std::cout << "Sorted array: ";
    for (int i : arr) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    return 0;
}

代码说明

  1. partition 函数:负责将数组分成两部分,并返回基准值的正确位置。
  2. quickSort 函数:递归地对数组进行排序。
  3. 时间复杂度
    • 平均情况下,快速排序的时间复杂度为 O(n log n)。
    • 最坏情况下(如数组已经有序),时间复杂度为 O(n²),但可以通过优化基准值的选择来避免。

优化

  1. 基准值选择:可以选择随机元素、中间元素或三数取中法来优化基准值的选择,避免最坏情况。
  2. 小数组优化:对于小数组,可以切换到插入排序,因为插入排序在小规模数据上表现更好。

希望这些代码能帮助您理解并解决这个问题,如果有问题,请随时提问。
  蒟蒻题解,神犇勿喷,点个赞再走吧!QAQ
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值