# 编程选择题

## 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)`。

要改善这种情况，可以采用随机化策略选择枢轴（如现在代码中所做的）或者使用“三点取中”的方式选择枢轴，以减少最坏情况发生的概率，从而保证算法的效率。
