From 71d60eaad8e7eef9fd1d1793dbbe4f26465566bc Mon Sep 17 00:00:00 2001 From: Zengtudor Date: Mon, 26 Aug 2024 13:39:50 +0800 Subject: [PATCH] update --- 20240826/CSP2020-senior/ans.txt | 2 +- 20240826/learn.md | 104 ++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 20240826/learn.md diff --git a/20240826/CSP2020-senior/ans.txt b/20240826/CSP2020-senior/ans.txt index ca72e74..827f5ff 100644 --- a/20240826/CSP2020-senior/ans.txt +++ b/20240826/CSP2020-senior/ans.txt @@ -16,4 +16,4 @@ 15.?D 二. 1. T F T F ?A ?A ?A -2. \ No newline at end of file +2. ?F ?F D A A D \ No newline at end of file diff --git a/20240826/learn.md b/20240826/learn.md new file mode 100644 index 0000000..87f340c --- /dev/null +++ b/20240826/learn.md @@ -0,0 +1,104 @@ +# 编程选择题 + +## 2 + +### 代码 + +```cpp +#include +#include +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)`。 + +要改善这种情况,可以采用随机化策略选择枢轴(如现在代码中所做的)或者使用“三点取中”的方式选择枢轴,以减少最坏情况发生的概率,从而保证算法的效率。