ProgramAlgTrain/20240826/learn.md
2024-08-26 13:39:50 +08:00

105 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 编程选择题
## 2
### 代码
```cpp
#include <iostream>
#include <cstdlib>
using namespace std;
int n;
int d[10000];
int find(int L, int R, int k) {
int x = rand() % (R - L + 1) + L;
swap(d[L], d[x]);
int a = L + 1, b = R;
while (a <= b) {
while (a <= b && d[a] < d[L])
++a;
while (a <= b && d[b] >= d[L])
--b;
if (a < b) {
swap(d[a], d[b]);
++a;
--b;
}
}
swap(d[L], d[b]);
if (b - L + 1 == k)
return d[b];
if (b - L + 1 < k)
return find(b + 1, R, k - (b - L + 1));
return find(L, b - 1, k);
}
int main() {
int k;
cin >> n;
cin >> k;
for (int i = 0; i < n; ++i)
cin >> d[i];
cout << find(0, n - 1, k) << endl;
return 0;
}
```
这段代码实现了**快速选择算法**Quickselect用于在未排序的数组中查找第 `k` 小的元素。
### 代码解释
1. **输入部分:**
- `n` 是数组 `d` 的大小。
- `k` 是我们要找的第 `k` 小的元素。
- 用户从输入中读取 `n``k`,然后读取 `n` 个整数填充数组 `d`
2. **`find` 函数:**
- 这个函数通过递归的方式实现了快速选择算法。其参数 `L``R` 定义了搜索的区间,`k` 是要查找的第 `k` 小元素。
3. **算法的核心逻辑:**
- 随机选取一个枢轴(`x`),将这个枢轴的值与 `d[L]` 交换,使得枢轴在 `d[L]` 处。
- 使用两个指针 `a``b`,分别从 `L+1` 向右和从 `R` 向左遍历,重排元素使得枢轴左边的元素都小于它,右边的元素都大于它。
- 最后,交换 `d[L]``d[b]`,使得 `d[b]` 成为枢轴元素,并且 `d[b]` 的位置是它在排序后应有的位置。
- 根据枢轴的位置 `b``k` 的关系:
- 如果 `b - L + 1 == k`,那么 `d[b]` 就是我们要找的第 `k` 小的元素。
- 如果 `b - L + 1 < k`,则说明第 `k` 小的元素在枢轴右侧,因此递归搜索 `b + 1``R` 区间,寻找第 `k - (b - L + 1)` 小的元素。
- 如果 `b - L + 1 > k`,则第 `k` 小的元素在枢轴左侧,因此递归搜索 `L``b - 1` 区间。
4. **程序的执行流程:**
- 主函数 `main` 读取输入后调用 `find` 函数,输出结果即为数组中第 `k` 小的元素。
### 代码功能总结
该代码的主要功能是在一个未排序的数组中寻找第 `k` 小的元素,使用快速选择算法,它的平均时间复杂度为 `O(n)`
如果数组中的数字是**单调递增**或**单调递减**`find` 函数仍然可以正确地找到第 `k` 小的元素,但在最坏情况下,算法的性能可能会变差。
### 单调递增的情况
在数组单调递增的情况下:
1. 假设数组是 `[1, 2, 3, ..., n]`,且我们要找第 `k` 小的元素。
2. 在每次调用 `find` 函数时,随机选择的枢轴 `x` 如果总是选择 `d[L]` 或者接近 `d[L]`,将会导致以下情况:
- 枢轴的值最小,每次都会将整个数组的一部分分给左边,右边是空的(`a` 会从 `L+1` 一路增加到 `R+1`)。
- 因此,每次递归只减少一个元素,导致 `find` 函数的递归深度达到 `n`,退化成类似于选择排序的行为,时间复杂度变为 `O(n^2)`
### 单调递减的情况
在数组单调递减的情况下:
1. 假设数组是 `[n, n-1, n-2, ..., 1]`,且我们要找第 `k` 小的元素。
2. 每次调用 `find` 函数时,随机选择的枢轴 `x` 如果总是选择 `d[L]` 或者接近 `d[L]`,会有类似单调递增的情况:
- 枢轴的值最大,所有其他元素都被分到左边,右边为空(`b` 会从 `R` 一路减少到 `L`)。
- 每次递归仍然只减少一个元素,导致递归深度达到 `n`,时间复杂度也退化为 `O(n^2)`
### 总结
虽然快速选择算法在随机数据或一般情况下具有 `O(n)` 的平均时间复杂度,但在输入数据是**单调递增**或**单调递减**且每次选择的枢轴都很不理想(例如总是选到最小或最大值)时,算法可能退化到最坏的情况,时间复杂度变为 `O(n^2)`
要改善这种情况,可以采用随机化策略选择枢轴(如现在代码中所做的)或者使用“三点取中”的方式选择枢轴,以减少最坏情况发生的概率,从而保证算法的效率。