线性时间选择[C++,附代码]
0 引言
问题:从无序数组中选择第k小的元素。
1 随机选择法
1.1 算法步骤:
-
选择基准元素:随机选择一个元素作为基准。
-
分区:对数组进行分区,使得基准元素左边的所有元素都小于它,右边的所有元素都大于它。分区过程完成后,我们得到了基准元素在数组中的位置pivotIndex。
-
递归选择:
如果pivotIndex == k - 1(注意数组索引从0开始),则基准元素就是第k小的元素。
如果pivotIndex < k - 1,则第k小的元素在右子数组中,递归地在右子数组中寻找第(k - pivotIndex - 1)小的元素。
如果pivotIndex > k - 1,则第k小的元素在左子数组中,递归地在左子数组中寻找第k小的元素(注意此时k值不变,因为我们在子数组中重新计数)。
1.2 算法图解
1.3 算法复杂度
随机选择法通过随机选取基准元素进行划分,平均时间复杂度为O(n),最坏时间复杂度为O(n^2)
1.4 代码实现
点击查看代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <cassert>
using namespace std;
/// <summary>
/// 输出向量
/// </summary>
/// <param name="c">输出流</param>
/// <param name="v">向量</param>
/// <returns>输出流</returns>
ostream& operator<<(ostream& c, vector<int> v)
{
cout << "[ ";
for (int a : v)
{
cout << a << ' ';
}
cout << "]\n";
return c;
}
/// <summary>
/// 分割序列,默认使用a[p]分割,使小于a[p]元素位于左侧,反之位于右侧
/// </summary>
/// <param name="a">序列,对a具有破坏性</param>
/// <param name="p">左下标</param>
/// <param name="r">右下标</param>
/// <returns>分割点下标</returns>
int Partition(vector<int>& a, int p, int r)
{
int i = p, j = r;
int x = a[p];
while (i<j)
{
while (i < j && a[i] < x)
{
++i;
}
while (i<j && a[j] > x)
{
--j;
}
swap(a[i], a[j]);
}
a[j] = x;
return j;
}
/// <summary>
/// 随机选择
/// </summary>
/// <param name="a">线性序列,对a有破坏性</param>
/// <param name="p">左下标</param>
/// <param name="r">右下标</param>
/// <param name="k">第k小元素</param>
/// <returns>第k小元素值</returns>
int RandomizedSelect(vector<int>& a, int p, int r, int k)
{
assert(k <= a.size());
int nSplit = Partition(a, p, r);
vector<int> vLeft(a.begin(), a.begin() + nSplit);
vector<int> vRight(a.begin() + nSplit + 1, a.end());
if (vLeft.size() == k - 1)
{
return a[nSplit];
}
else if (k <= vLeft.size())
{
return RandomizedSelect(vLeft, 0, vLeft.size() - 1, k);
}
else
{
return RandomizedSelect(vRight, 0, vRight.size() - 1, k - vLeft.size() - 1);
}
}
int main()
{
vector<int> vec({ 10,2,32,45,21,33,8,36,55,64,12,78,5 });
cout << "原序列" << vec;
int k = 5;
int nV = RandomizedSelect(vec, 0, vec.size() - 1, k);
sort(vec.begin(), vec.end());
cout << "排序后:" << vec;
cout << "第" << k << "小元素为:" << nV << endl;
return 0;
}
点击查看运行结果
原序列[ 10 2 32 45 21 33 8 36 55 64 12 78 5 ]
排序后:[ 2 5 8 10 12 21 32 33 36 45 55 64 78 ]
第7小元素为:32
D:\Visual Studio 2019\项目\算法分析与设计\Debug\算法分析与设计.exe (进程 5476)已退出,代码为 0。
按任意键关闭此窗口. . .