————致力于用代码改变世界

线性时间选择[C++,附代码]

0 引言

问题:从无序数组中选择第k小的元素。

1 随机选择法

1.1 算法步骤:

  1. 选择基准元素:随机选择一个元素作为基准。

  2. 分区:对数组进行分区,使得基准元素左边的所有元素都小于它,右边的所有元素都大于它。分区过程完成后,我们得到了基准元素在数组中的位置pivotIndex。

  3. 递归选择:
    如果pivotIndex == k - 1(注意数组索引从0开始),则基准元素就是第k小的元素。
    如果pivotIndex < k - 1,则第k小的元素在右子数组中,递归地在右子数组中寻找第(k - pivotIndex - 1)小的元素。
    如果pivotIndex > k - 1,则第k小的元素在左子数组中,递归地在左子数组中寻找第k小的元素(注意此时k值不变,因为我们在子数组中重新计数)。

1.2 算法图解

image

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。
按任意键关闭此窗口. . .

posted @ 2024-11-27 16:01  hello_nullptr  阅读(11)  评论(0编辑  收藏  举报