题解:洛谷 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
【核心思想】
-
问题分析:给定一个长度为 \(n\)(\(n \le 5\times10^6\))的数组,要求找出第 \(k\) 小的数(\(k\) 从 0 开始计数)。由于 \(n\) 较大,不能对整个数组排序,需要使用快速选择算法(基于快速排序的分治思想),在平均 \(O(n)\) 时间内找到第 \(k\) 小的元素。
-
算法选择:
- 快速选择:利用快速排序的分区操作,每轮确定基准值的最终位置
p。若p == k,则找到答案;若k < p,在左半部分递归;否则在右半部分递归。只需递归一侧,平均时间复杂度为 \(O(n)\)。 - 优化:可使用随机基准或三数取中避免最坏情况,但本题数据下中间基准足够。
- 快速选择:利用快速排序的分区操作,每轮确定基准值的最终位置
-
关键步骤:
- 读入:使用
ios::sync_with_stdio(false)加速输入,读取 \(n\) 和 \(k\),以及数组 \(a[0..n-1]\)。 - 快速选择函数:
quickSelect(l, r):- 若
l >= r,返回。 - 选择基准
pivot = a[(l+r)/2],双指针i = l,j = r。 - 循环:
while (a[i] < pivot) i++,while (a[j] > pivot) j--,若i <= j则交换并移动指针。 - 分区结束后,
j和i将数组分为三部分:[l..j](\(\le\) pivot),[j+1..i-1](\(=\) pivot),[i..r](\(\ge\) pivot)。 - 若
k <= j,递归左部分;若k >= i,递归右部分;否则,k在当前中间部分,直接返回。
- 若
- 输出:
a[k]。
- 读入:使用
-
时间/空间复杂度:
- 时间复杂度:平均 \(O(n)\),最坏 \(O(n^2)\)(但可优化)。
- 空间复杂度:\(O(\log n)\),递归栈深度。
-
快速选择 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
浙公网安备 33010602011771号