题解:洛谷 P1923 求第k小的数

【题目来源】

洛谷:P1923 【深基9.例4】求第 k 小的数 - 洛谷(luogu.com.cn)

【题目描述】

输入 \(n\)\(1\le n\lt 5000000\)\(n\) 为奇数)个数字 \(a_i(1\le a_i\lt 10^9)\),输出这些数字的第 \(k\) 小的数。最小的数是第 \(0\) 小。

请尽量不要使用 nth_element 来写本题,因为本题的重点在于练习分治算法。

【输入】

【输出】

【输入样例】

5 1
4 3 2 1 5

【输出样例】

2

【核心思想】

  1. 问题分析:给定一个长度为 \(n\)\(n \le 5\times10^6\))的数组,要求找出第 \(k\) 小的数(\(k\) 从 0 开始计数)。由于 \(n\) 较大,不能对整个数组排序,需要使用快速选择算法(基于快速排序的分治思想),在平均 \(O(n)\) 时间内找到第 \(k\) 小的元素。

  2. 算法选择

    • 快速选择:利用快速排序的分区操作,每轮确定基准值的最终位置 p。若 p == k,则找到答案;若 k < p,在左半部分递归;否则在右半部分递归。只需递归一侧,平均时间复杂度为 \(O(n)\)
    • 优化:可使用随机基准或三数取中避免最坏情况,但本题数据下中间基准足够。
  3. 关键步骤

    • 读入:使用 ios::sync_with_stdio(false) 加速输入,读取 \(n\)\(k\),以及数组 \(a[0..n-1]\)
    • 快速选择函数quickSelect(l, r)
      • l >= r,返回。
      • 选择基准 pivot = a[(l+r)/2],双指针 i = lj = r
      • 循环:while (a[i] < pivot) i++while (a[j] > pivot) j--,若 i <= j 则交换并移动指针。
      • 分区结束后,ji 将数组分为三部分:[l..j]\(\le\) pivot),[j+1..i-1]\(=\) pivot),[i..r]\(\ge\) pivot)。
      • k <= j,递归左部分;若 k >= i,递归右部分;否则,k 在当前中间部分,直接返回。
    • 输出a[k]
  4. 时间/空间复杂度

    • 时间复杂度:平均 \(O(n)\),最坏 \(O(n^2)\)(但可优化)。
    • 空间复杂度:\(O(\log n)\),递归栈深度。
  5. 快速选择 vs 排序

    • 核心优势:只递归一侧,平均时间复杂度为 \(O(n)\),比 \(O(n \log n)\) 的排序更快,尤其适合 \(n\) 很大的情况。
    • 适用场景:寻找第 \(k\) 大/小的元素、中位数等。
    • 注意\(k\) 从 0 开始计数,与数组下标一致。

【算法标签】

普及- #快速排序

【代码详解】

#include <bits/stdc++.h>  // 包含标准库中的所有头文件
using namespace std;      // 使用标准命名空间

int n, k, a[5000005];     // 定义全局变量n(数组长度)、k(目标位置)、a(数组)

// 快速选择函数,用于找到第k小的元素
void qs(int l, int r) {
    int i = l, j = r;              // 初始化左右指针
    int mid = a[(l + r) / 2];      // 选择中间元素作为基准值(pivot)

    // 分区操作
    while (i <= j) {
        while (a[i] < mid) i++;    // 从左向右找到第一个大于等于基准值的元素
        while (a[j] > mid) j--;    // 从右向左找到第一个小于等于基准值的元素
        if (i <= j) {              // 如果i和j没有交叉
            swap(a[i], a[j]);      // 交换这两个元素
            i++;                   // 移动左指针
            j--;                   // 移动右指针
        }
    }

    // 递归处理目标区间
    if (k <= j) qs(l, j);          // 如果k在左半部分,递归处理左半部分
    else if (k >= i) qs(i, r);     // 如果k在右半部分,递归处理右半部分
    else if (k >= j && k <= i) return;  // 如果k在当前区间内,直接返回
}

int main() {
    std::ios::sync_with_stdio(false);  // 关闭同步,加速输入输出
    cin >> n >> k;                     // 输入数组长度n和目标位置k
    for (int i = 0; i < n; i++) cin >> a[i];  // 输入数组元素

    qs(0, n - 1);  // 调用快速选择函数,找到第k小的元素

    cout << a[k];  // 输出第k小的元素
    return 0;      // 程序正常结束
}

【运行结果】

5 1
4 3 2 1 5
2
posted @ 2026-08-31 09:25  团爸讲算法  阅读(12)  评论(0)    收藏  举报