findKthSmallest()
函数是寻找第 k 小值的主函数,它接收待排序的数组,数组的长度以及要寻找的第 k 小的数为输入。
在 findKthSmallest()
函数中,我们首先判断 k 的值是否在 1 和 n 之间,若不在则返回错误信息。否则,调用 findKthSmallestRec()
函数递归寻找数组中第 k 小的数。
在 findKthSmallestRec()
函数中,首先对数组进行划分,并返回枢轴元素的位置,然后比较枢轴元素从左开始到此时的长度是否等于 k,如果是的话,就找到了第 k 小的数。如果枢轴左边的元素的总数小于 k,则在枢轴右边的数组中递归查找第 (k - length) 小的元素。如果枢轴左边的元素的总数大于 k,则在枢轴左边的数组中递归查找第 k 小的元素。
最后,partition()
函数是快速排序中的 partition 操作,将待排序的数组划分为两个部分,返回枢轴的位置。注意到这里的 partition()
函数与快速排序算法中的 partition()
函数实现相同。
// 寻找第 k 小的元素
int findKthSmallest(int arr[], int n, int k) {
// 如果 k 的值不在 1~n 之间,返回错误信息
if (k < 1 || k > n) {
printf("Error: k is out of range.\n");
return -1;
}
// 否则调用递归寻找第 k 小的数
return findKthSmallestRec(arr, 0, n - 1, k);
}
// 递归划分数组,寻找第 k 小的数
int findKthSmallestRec(int arr[], int left, int right, int k) {
// 如果数组只有一个元素,那么该元素就是第 k 小的数
if (left == right) {
return arr[left];
}
// 对 arr[left..right] 进行划分
int pivotIndex = partition(arr, left, right);
// 计算枢轴元素左边的子数组长度
int length = pivotIndex - left + 1;
// 如果枢轴元素左边的子数组长度等于 k,则该枢轴元素就是第 k 小的数
if (k == length) {
return arr[pivotIndex];
}
// 如果枢轴元素左边的子数组长度大于 k,则递归在枢轴元素左边的子数组寻找第 k 小的数
else if (k < length) {
return findKthSmallestRec(arr, left, pivotIndex - 1, k);
}
// 如果枢轴元素左边的子数组长度小于 k,则递归在枢轴元素右边的子数组寻找第 (k - length) 小的数
else {
return findKthSmallestRec(arr, pivotIndex + 1, right, k - length);
}
}
// 划分数组,返回枢轴的位置
int partition(int arr[], int left, int right) {
// 将 arr[right] 选为枢轴元素
int pivot = arr[right];
// 定义下标 i,表示枢轴元素的位置的左边都小于等于 pivot
int i = left - 1;
// 遍历 arr[left..right-1]
for (int j = left; j < right; j++) {
// 如果 arr[j] 小于等于枢轴元素,则将其放在左侧
if (arr[j] <= pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
// 将枢轴元素放在划分点位置,将其左边的元素都小于等于枢轴元素,右边的元素都大于等于枢轴元素
swap(&arr[i + 1], &arr[right]);
// 返回枢轴元素的位置
return i + 1;
}
// 交换两个元素的值
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
在最优、平均和最坏情况下,寻找第 k 小的数的复杂度方程不同。
最优情况下,每次划分都得到两个长度相等的子数组,此时时间复杂度为:
T(n)=2T(n/2)+O(n)
根据主定理,最优情况下的时间复杂度为 O(nlogn)。
平均情况下,我们假设每次划分得到的两个子数组的大小相同,此时时间复杂度为:
T(n)=T(n/2)+O(n)
根据主定理,平均情况下的时间复杂度为 O(n)。
最坏情况下,每次划分得到的一个子数组为空,而另一个子数组的长度为 n−1。在最坏情况下,每次都需要遍历整个数组,此时时间复杂度为:
T(n)=T(n−1)+O(n)
将递归展开:
T(n)=T(n−1)+O(n)=T(n−2)+O(n−1)+O(n)=T(n−3)+O(n−2)+O(n−1)+O(n)⋯
可以得到最坏情况下的时间复杂度为 O(n^2)。