434
社区成员
发帖
与我相关
我的任务
分享计科2301谢晶 20220200591
1、代码:
#include<iostream>
using namespace std;
//划分函数,将数组划分为两部分,左边部分<=x,右边部分>=x
//返回x最终所在的位置(划分后的中间位置)
int partition(int a[], int l, int r)
{
int left=l;
int right=r;
int x = a[left]; // 选择最左边的元素作为基准x
while (left < right) // 当左右指针没有相遇时继续划分
{
while (left < right && a[right] >= x)
{ right--; }// 从右向左找第一个<x的元素,如果找到了,将其放到左边位置
if (left < right)//如果x>a[right]
{ a[left] = a[right]; }
while (left < right && a[left] <= x)
{ left++; }// 从左向右找第一个>x的元素,如果找到了,将其放到右边位置
if (left < right)
{ a[right] = a[left]; }
}
a[left] = x; //a[right], a[left]同时指向一个位置, 将基准x放上去
return left; // 返回x的位置(划分后的中间位置)
}
// 快速选择函数,在数组a的left到right范围内找到第k小的元素
//每次递归处理n/2个元素,平均时间为O(n)
void find(int a[], int left, int right, int k)
{
int p = partition(a, left, right); // 对数组进行划分
//数组索引从0开始,第k小的元素对应的索引是k-1
if ((k - 1) == p)//如果k-1等于划分的位置,说明p就是第k小的元素,直接输出
{cout << a[k - 1];}
else if ((k - 1) > p)//如果k-1大于划分的位置,说明第k小的元素在p的右侧,在右半部分继续查找
{find(a, p + 1, right, k);
//数组索引是从0开始的,所以右侧起始索引是p+1 }
else // 如果k-1小于划分的位置,说明第k小的元素在p的左侧,在左半部分继续查找
{find(a, left, p - 1, k);
//p是基准元素的位置,它不包含在左侧数组中,所以左侧结束索引是p-1
}
int main()
{
int n, k, num[10001]; // 定义数组num用于存储输入的n个元素和变量
cin >> n >> k;
for (int i = 0; i < n; i++)
{ cin >> num[i]; // 输入n个元素 }
find(num, 0, n - 1, k);
return 0;
}
首先我们要判断左右是否相等,如果相等就直接返回这个数。在不相遇的情况下我们继续讨论,当a[right] >= x,我们从右向左找第一个<x的元素,如果找到了,将其放到左边位置。当a[left] <= x,我们从左向右找第一个>x的元素,如果找到了,将其放到右边位置。继续循环,直到a[right], a[left]同时指向一个位置, 我们将基准x放上去。对数组进行划分,如果k-1等于划分的位置,说明p就是第k小的元素,直接输出,如果k-1大于划分的位置,说明第k小的元素在p的右侧,在右半部分继续查找,如果k-1小于划分的位置,说明第k小的元素在p的左侧,在左半部分继续查找。
2、
最好的时间复杂度:O(n),每次递归处理n/2个元素。
最坏的时间复杂度:O(n^2),如果基准元素选择不当,数组的第一个或最后一个元素作为基准。
3、
我了解到,分治法是一种解决问题的方法,它能将一个复杂的问题分解为若干个较小的、更易于解决的子问题。用递归地解决这些子问题,最后将这些子问题的解合并起来以得到原问题的解。比如说在这次的快速选择算法中,问题被分解为两个子问题:在基准元素的左侧查找第k小的元素,或在基准元素的右侧查找第k-p-1小的元素。这样的分解会比原来的问题更加简易。此外,我还学习到基准元素选择的重要性,如果基准元素选择得当,可以使每次划分都更加平衡,从而减少比较次数。如果选择不当,就会使得时间复杂度更坏。总之,通过深入的理解和学习分治法,我可以更好地学会如何解决更加复杂的问题,并学习到更高效的算法。