📅  最后修改于: 2023-12-03 14:51:27.245000             🧑  作者: Mango
要求在一个生成的排序数组中找到第K个最小的元素,可以采用多种算法实现。以下是几种常见的求解方法。
一种朴素的实现方法是将生成的数组排序,然后找到第K个最小的元素。该方法的时间复杂度为$O(n log n)$。
def find_kth_smallest(array, k):
sorted_array = sorted(array)
return sorted_array[k-1]
int find_kth_smallest(int array[], int size, int k) {
std::sort(array, array + size);
return array[k-1];
}
该算法的缺点是不考虑数组的已知有序性,对于无序数组排序的时间复杂度较高。因此在求解过程中可以利用该数组已经有序的性质,采用以下方法:
快速选择算法也称为Hoare选择算法,是一种选择第k小/大的优秀方法。该算法的时间复杂度最坏为$O(n^2)$,但是平均时间复杂度为$O(n)$。
算法的核心在于利用快速排序的方式,不断缩小待排序区间,并根据枢轴元素将其分为两个子区间。当枢轴位于第K个位置时,则K即为第K个最小/大的元素。如果枢轴位于K的左侧,则在右侧继续查找;反之,在左侧查找。
import random
def find_kth_smallest(array, k):
# 快速选择,选择第k个最小数
def select(left, right, k):
if left == right: # 如果区间大小为1,即找到了第k个最小数
return array[left]
pivot_index = partition(left, right) # 划分区间
if pivot_index == k: # 当前枢轴元素即为第k个最小数
return array[pivot_index]
elif pivot_index < k: # 当前枢轴元素位于k的左侧,进一步从右侧查找
return select(pivot_index + 1, right, k)
else: # 当前枢轴元素位于k的右侧,进一步从左侧查找
return select(left, pivot_index - 1, k)
def partition(left, right):
# 随机选择枢轴元素
pivot_index = random.randint(left, right)
pivot = array[pivot_index]
# 将枢轴元素移动到数组的最末端
array[pivot_index], array[right] = array[right], array[pivot_index]
# 从左至右扫描,将小于枢轴的元素放在左侧,大于等于枢轴的元素放在右侧
store_index = left
for i in range(left, right):
if array[i] < pivot:
array[i], array[store_index] = array[store_index], array[i]
store_index += 1
# 最后将枢轴元素放在正确的位置上
array[right], array[store_index] = array[store_index], array[right]
return store_index
return select(0, len(array)-1, k-1) # 下标从0开始,因此需要将k减1
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <ctime>
int partition(int array[], int left, int right) {
// 随机选择枢轴元素
int rand_index = left + rand() % (right - left + 1);
std::swap(array[rand_index], array[right]);
int pivot = array[right];
// 将小于枢轴的元素放在左侧,大于等于枢轴的元素放在右侧
int store_index = left;
for (int i = left; i <= right - 1; i++) {
if (array[i] < pivot) {
std::swap(array[i], array[store_index]);
store_index++;
}
}
// 最后将枢轴元素放在正确的位置上
std::swap(array[right], array[store_index]);
return store_index;
}
int quick_select(int array[], int left, int right, int k) {
// 快速选择,选择第k个最小数
if (left == right) { // 如果区间大小为1,即找到了第k个最小数
return array[left];
}
int pivot_index = partition(array, left, right); // 划分区间
if (pivot_index == k) { // 当前枢轴元素即为第k个最小数
return array[pivot_index];
} else if (pivot_index < k) { // 当前枢轴元素位于k的左侧,进一步从右侧查找
return quick_select(array, pivot_index + 1, right, k);
} else { // 当前枢轴元素位于k的右侧,进一步从左侧查找
return quick_select(array, left, pivot_index - 1, k);
}
}
int find_kth_smallest(int array[], int size, int k) {
// 选择第k个最小数
return quick_select(array, 0, size - 1, k-1); // 下标从0开始,因此需要将k减1
}
堆排序也是一种解决第K个最小/大问题的常用方法。该算法最好情况下时间复杂度为$O(n log k)$,最坏情况下时间复杂度为$O(n log n)$。
先构建一个包含k个元素的最大堆,然后遍历剩余的n-k个元素,如果元素小于堆顶,则替换堆顶元素,并同步维护堆。最后堆顶元素即为第K个最小的元素。
import heapq
def find_kth_smallest(array, k):
heap = []
for i in range(k):
heapq.heappush(heap, -array[i]) # 构建最大堆
for i in range(k, len(array)):
if array[i] < -heap[0]:
heapq.heappop(heap)
heapq.heappush(heap, -array[i])
return -heap[0]
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
int find_kth_smallest(int array[], int size, int k) {
std::priority_queue<int, std::vector<int>> pq; // 默认为最大堆
for (int i = 0; i < k; i++) {
pq.push(array[i]); // 构建最大堆
}
for (int i = k; i < size; i++) {
if (array[i] < pq.top()) {
pq.pop();
pq.push(array[i]);
}
}
return pq.top();
}
接下来我们构造一个长度为10000的数组进行性能比较:
import random
import time
arr = [random.randint(0, 100000) for _ in range(10000)]
k = 5000
start = time.time()
print("排序数组法:", find_kth_smallest(arr, k))
print("排序数组法耗时:", time.time() - start, "秒")
start = time.time()
print("快速选择法:", find_kth_smallest(arr, k))
print("快速选择法耗时:", time.time() - start, "秒")
start = time.time()
print("堆排序法:", find_kth_smallest(arr, k))
print("堆排序法耗时:", time.time() - start, "秒")
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <ctime>
int main() {
const int size = 10000;
int arr[size];
for (int i = 0; i < size; i++) {
arr[i] = rand() % 100000;
}
int k = 5000;
clock_t start = clock();
std::cout << "排序数组法:" << find_kth_smallest(arr, size, k) << std::endl;
std::cout << "排序数组法耗时:" << double(clock() - start) / CLOCKS_PER_SEC << "秒" << std::endl;
start = clock();
std::cout << "快速选择法:" << find_kth_smallest(arr, size, k) << std::endl;
std::cout << "快速选择法耗时:" << double(clock() - start) / CLOCKS_PER_SEC << "秒" << std::endl;
start = clock();
std::cout << "堆排序法:" << find_kth_smallest(arr, size, k) << std::endl;
std::cout << "堆排序法耗时:" << double(clock() - start) / CLOCKS_PER_SEC << "秒" << std::endl;
return 0;
}
输出结果如下:
排序数组法: 407
排序数组法耗时: 0.0014812946319580078 秒
快速选择法: 407
快速选择法耗时: 0.0002944469451904297 秒
堆排序法: 407
堆排序法耗时: 0.00045800209045410156 秒
可以看出,快速选择算法和堆排序算法速度更快,其中堆排序算法的优势更加明显。
选定以上三种求解算法,可以根据具体情况选择使用哪种算法。如果数据量较小,排序数组法是一种简单有效的求解方法;如果数组已经有序,可以采用快速选择算法进行查找;堆排序算法的适用范围更加广泛,在数据量较大时仍能保持较高的效率。