// 根据基准点划分区间 intpartition(int a[], int p, int r){ int i = p, j = r + 1; int x = a[p]; // 基准元素 while (true) { while (a[++i] < x && i < r) {} // 从左向右找第一个大于等于 x 的元素, 注意边界 while (a[--j] > x && j > l) {} // 从右向左找第一个小于等于 x 的元素, 注意边界 if (i >= j) break; // 左右指针汇合 swap(a[i], a[j]); } a[p] = a[j]; // 将分元素放到正确的位置 a[j] = x; // 将基准元素放到正确位置 return j; // 返回基准位置 }
intselect(int a[], int p, int r, int k){ if (p == r) return a[p]; // 最小问题规模:只有一个元素
int i = partition(a, p, r); // 调用划分函数进行划分 int j = i - p + 1; // 计算新基准点的在现区间的次序
// 根据划分结果进行查找 if (j == k) return a[i]; elseif (j > k) returnselect(a, p, i - 1, k); else returnselect(a, i + 1, r, k - j); }
constint MAXN = 100; // 设置最大 n 的范围 int a[MAXN]; // 数组
// 划分函数 intpartition(int a[], int l, int r) { // 选择基准元素 int i = l, j = r + 1; int x = a[l];
// 划分区间 while (true) { while (a[++i] < x && i < r) // 从左向右找第一个大于等于 x 的元素, 注意边界 ; while (a[--j] > x && j > l) // 从右向左找第一个小于等于 x 的元素, 注意边界 ; if (i >= j) // 如果两个指针相遇,则退出循环 break; swap(a[i], a[j]); // 交换元素 } // 此时 j 指向分界点元素 a[l] = a[j]; // 将分界点元素放到正确的位置 a[j] = x; // 将基准元素放到最终位置 return j; // 返回分界点位置 }
// 随机选择基准值 intrandomPartition(int a[], int l, int r) { int i = l + rand() % (r - l + 1); // 随机选择一个元素作为分界点 int temp = a[i]; // 将分界点元素与最前一个元素交换 a[i] = a[l]; a[l] = temp; returnpartition(a, l, r); // 调用划分函数 }
// 计算第 k 小的元素 intrandomSelect(int a[], int l, int r, int k) { if (l == r) return a[l]; int i = randomPartition(a, l, r); // 随机选择分界点 int j = i - l + 1; // 计算新基准点的在现区间的次序
// 根据划分结果进行查找 if (j == k) return a[i]; elseif (j > k) returnrandomSelect(a, l, i - 1, k); else returnrandomSelect(a, i + 1, r, k - j); }
// 主函数 intmain(){ int n, k; cin >> n >> k;
for(int i = 0; i < n; ++i){ cin >> a[i]; }
// 求第k个数 int res = select(a, 0, n - 1, k);
cout << res << endl;
return0;
⚠️值得注意的
partition函数中的数组越界问题
添加i和j的越界检查:
&& i < r检查左指针i是否越右界
&& j > p检查右指针是否越左界
select函数中关系选择正确的区间:
递归选择条件分析
划分操作: partition 方法在 a[i] 处划分数组,使得 a[p...i−1]<=a[i]<a[i+1...r]。变量 j 的意义: j = i - p + 1 表示 a[i] 在当前子数组(从 p 到 r)中的第几小的位置(位置是相对于1开始)。递归部分的问题:
条件 j >= k:
这种条件会导致当 j == k 时进入错误的区间选择。在 j >= k 中,当 j == k 时已经找到所需元素 a[i],因此直接返回 a[i] 才是正确的选择。
右区间调用:
else 部分继续在右区间进行,减少k后递归,这部分通常是正确的。
修正:
根据 j 和 k 的关系选择正确的区间:当 j == k 时,返回 a[i]。当 j > k 时,递归在左区间 [p, i-1]。当 j < k 时,递归在右区间 [i+1, r],但此时需要调整 k 为 k - j。