沥川的算法学习笔记:基础算法(1)----快速排序

发布于:2024-10-18 ⋅ 阅读:(6) ⋅ 点赞:(0)

1.快速排序

        快速排序是一种高效的排序算法,它利用了分治的思想。快速排序的基本思想是选择一个基准元素,将数组分成两个子数组,其中一个子数组的元素都小于等于基准元素,另一个子数组的元素都大于等于基准元素,然后对这两个子数组递归地应用快速排序算法。最后将两个子数组合并起来得到排序后的数组。

快速排序的具体步骤如下:

  1. 选择一个基准元素,通常是数组中的第一个元素或者随机选择。
  2. 将数组分成两个子数组,左边的子数组中的元素都小于等于基准元素,右边的子数组中的元素都大于等于基准元素。可以使用双指针的方式,一个指针从左边开始,一个指针从右边开始,然后交换两个指针所指向的元素,直到两个指针相遇。
  3. 对左右两个子数组递归地应用快速排序算法。
  4. 将左右两个子数组合并起来得到排序后的数组。

        快速排序的时间复杂度为O(nlogn),其中n为数组的大小。快速排序是一种原地排序算法,不需要额外的空间。但是快速排序是一种不稳定的排序算法,即相同元素的相对位置可能会发生变化。

#include <iostream> // 引入标准输入输出流库
using namespace std; // 使用标准命名空间

const int N=1e6+10; // 定义常量N,表示数组的最大容量,1e6+10即1000000+10
int n; // 定义变量n,用于存储待排序元素的数量
int q[N]; // 定义整型数组q,用于存储待排序的元素

// 快速排序函数
void quick_sort(int q[], int l, int r) {
    // 如果左边界索引大于等于右边界索引,说明子数组已经有序,直接返回
    if(l >= r) return;

    // 选择子数组的第一个元素作为基准
    int x = q[l];
    // 初始化两个指针,i指向左边界的前一个位置,j指向右边界后一个位置
    int i = l - 1, j = r + 1;

    // 当i和j没有相遇时,继续循环
    while(i < j) {
        // 从左向右移动i,直到找到一个大于等于基准的元素
        do {
            i++;
        } while (q[i] < x);

        // 从右向左移动j,直到找到一个小于等于基准的元素
        do {
            j--;
        } while (q[j] > x);

        // 如果i和j没有相遇,交换它们指向的元素
        if(i < j) swap(q[i], q[j]);
    }

    // 递归地对基准左侧的子数组进行快速排序
    quick_sort(q, l, j);
    // 递归地对基准右侧的子数组进行快速排序
    quick_sort(q, j + 1, r);
}

int main() {
    // 从标准输入读取元素数量
    scanf("%d", &n);
    // 从标准输入读取待排序的元素
    for(int i = 0; i < n; i++) {
        scanf("%d", &q[i]);
    }
    // 调用快速排序函数对数组进行排序
    quick_sort(q, 0, n - 1);
    // 将排序后的数组输出到标准输出
    for(int i = 0; i < n; i++) {
        printf("%d ", q[i]);
    }
    // 程序结束,返回0
    return 0;
}

        选择不同位置的基准元素会影响快速排序的性能。基准元素的选择可以影响递归过程中数组的划分效果,从而影响算法的时间复杂度和稳定性。

  1. 若选择第一个或最后一个元素作为基准元素:

    • 最好情况:如果数组已经有序或接近有序,选择第一个或最后一个元素作为基准元素可以得到较好的划分效果,此时快速排序的时间复杂度接近O(nlogn)。
    • 最坏情况:如果数组已经有序或接近有序,并且每次选择第一个或最后一个元素作为基准元素,则快速排序的时间复杂度会退化为O(n^2),这是因为每次划分只能将数组分成两个部分,其中一个部分为空,没有减少问题的规模。
    • 平均情况:平均情况下,选择第一个或最后一个元素作为基准元素可以得到较好的划分效果,快速排序的时间复杂度为O(nlogn)。
  2. 若选择随机位置的元素作为基准元素:

    • 通过随机选择基准元素,可以降低最坏情况的概率,避免快速排序时间复杂度退化为O(n^2)。
    • 选择随机位置的元素作为基准元素可以提高快速排序的平均性能,快速排序的时间复杂度为O(nlogn)。

        

        

                ​​​​​​​        

2.第K个数----类快排问题

        给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数。

输入格式

        第一行包含两个整数 n 和 k。

        第二行包含 n 个整数(所有整数均在 1∼1091∼109 范围内),表示整数数列。

输出格式

        输出一个整数,表示数列的第 k 小数。

分析

        本题实际上就是在快速排序的基础上多了找到第k个的元素,实现思路和方法与快排一致:

#include <iostream> // 引入标准输入输出流库
using namespace std; // 使用标准命名空间

const int N=1e6+10; // 定义常量N,表示数组的最大容量,1e6+10即1000000+10
int n; // 定义变量n,用于存储待排序元素的数量
int q[N]; // 定义整型数组q,用于存储待排序的元素

// 快速排序函数
void quick_sort(int q[], int l, int r) {
    // 如果左边界索引大于等于右边界索引,说明子数组已经有序,直接返回
    if(l >= r) return;

    // 选择子数组的中间元素作为基准
    int x = q[(l+r)/2];
    // 初始化两个指针,i指向左边界的前一个位置,j指向右边界后一个位置
    int i = l - 1, j = r + 1;

    // 当i和j没有相遇时,继续循环
    while(i < j) {
        // 从左向右移动i,直到找到一个大于等于基准的元素
        do {
            i++;
        } while (q[i] < x);

        // 从右向左移动j,直到找到一个小于等于基准的元素
        do {
            j--;
        } while (q[j] > x);

        // 如果i和j没有相遇,交换它们指向的元素
        if(i < j) swap(q[i], q[j]);
    }

    // 递归地对基准左侧的子数组进行快速排序
    quick_sort(q, l, j);
    // 递归地对基准右侧的子数组进行快速排序
    quick_sort(q, j + 1, r);
}

int main() {
    // 从标准输入读取元素数量
    scanf("%d", &n);
    // 从标准输入读取待排序的元素
    for(int i = 0; i < n; i++) {
        scanf("%d", &q[i]);
    }
    // 调用快速排序函数对数组进行排序
    quick_sort(q, 0, n - 1);
    // 将排序后的数组输出到标准输出
    for(int i = 0; i < n; i++) {
        printf("%d ", q[i]);
    }
    // 程序结束,返回0
    return 0;
}