课堂笔记---线性选择

问题描述
给定线性序集中n 个元素和一个正数k ,1 ≤ k ≤ n ,要求找出这n个元素中第k大的元素

算法描述:
对于该问题的解决可采用一种类似于快速排序的划分的方法。

最开始我们将所有的元素五个一组,分为n/5个组,然后对于每组无论采用哪种排序算法进行排序,找出其中位数,最后找出共n/5个中位数。

递归调用该算法找出这n/5个中位数的中位数。(如果n/5为偶数的话,就找出其较大的中位数)

然后以该元素作为划分基准,将所有的中位数进行划分。

如图所示

此时已经将所有的分组按照其中位数进行划分。

将所有元素以基准为中心划为四个区域

此时所有处于左下区域的元素都小于基准。右上区域的元素都大于基准。

即至少判断得到⌊n/10⌋*3个数字小于基准,5/10⌋*3个数字大于基准。

此时⌊n/10⌋*3>n/4 

经过该方法,每次递归至少可以省区1/4的元素。

复杂度分析如下:

在此,我们的算法的递归调用只有在n>75时执行,当小于75时我们通过简单的排序然后输出第k大的数。

⌊n/10⌋*3>n/4 

若需要对左下区域的元素进行递归,则复杂度为为T(n/4)

否则需要T(4*n/3)

另外我们得到无论是什么样的数据,当n<75是,其复杂度不会超过一个常数。

由以上可得:

解此递归式可得T(n)=O(n);

#include<iostream>
#include<cstdio>
#include<random>


using namespace std;

int n, k, len;

//选择排序
void SelectSort(int a[], int p, int r);
//将x作为基准数将数组分割,返回x的位置
int Partition(int a[], int p, int r, int x);
//交换两个元素
void Swap(int& a, int& b);
//找每组的中位数,返回中位数的位置i
int SearchMid(int a[], int p, int r);
//线性划分
int Select(int a[], int p, int r, int k);

int main()
{
    //输入数组长度n
    cin >> n;
    //输入数组
    int* a = new int[n];
    for (int i = 0; i < n; ++i)
    {
        cin >> a[i];
    }
    //输入第k小
    cin >> k;
    //找第k小并返回
    int res = Select(a, 0, n - 1, k);
    cout << res << endl;
    delete[]a;
    return 0;
}

void SelectSort(int a[], int p, int r)
{
    for (int i = p; i < r; ++i)
    {
        int index = i;
        for (int j = i + 1; j <= r; ++j)
        {
            if (a[j] < a[index])
            {
                index = j;
            }
        }
        Swap(a[i], a[index]);
    }
}


int Partition(int a[], int p, int r, int x)
{
    //i指向首元素的前一个位置,j指向尾元素的后一个位置
    int i = p - 1, j = r + 1;
    while (1)
    {
        //i从基准数右边的元素开始找,直到找到第一个大于等于基准数的元素
        while (a[++i] < x && i < r);
        //j从尾元素开始找,直到找到第一个小于等于基准数的元素
        while (a[--j] > x && j > p);
        //若i>=j,说明基准数的位置已找到,为j
        if (i >= j)
        {
            break;
        }
        //交换两个元素,使得基准数左边的数均不大于它,右边的数均不小于它
        Swap(a[i], a[j]);
    }
    //返回基准数的位置
    return j;
}

void Swap(int& a, int& b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}

int SearchMid(int a[], int p, int r)
{
    //建立与数组a同等大小的数组b
    int* b = new int[r - p + 1];
    //用数组b存放数组a(注意此时b的首地址为0,而a的首地址为p)
    for (int i = p; i <= r; ++i)
    {
        b[i - p] = a[i];
    }
    //将数组b排序,b[(r-p+1)/2]为中位数
    SelectSort(b, 0, r - p);
    for (int i = p; i <= r; ++i)
    {
        if (a[i] == b[(r - p + 1) / 2])
        {
            return i;
        }
    }
    delete[]b;
    return 0;
}

int Select(int a[], int p, int r, int k)
{
    if (r - p < 5)
    {
        SelectSort(a, p, r);
        return a[p + k - 1];
    }
    //分成n/5组,每组5个,找到每组的中位数并将它放到数组首元素的位置
    for (int i = 0; i <= (r - p - 4) / 5; ++i)
    {
        int mid = SearchMid(a, p + 5 * i, p + 5 * i + 4);
        Swap(a[mid], a[p + i]);
    }
    //找到各组中位数的中位数
    int x = Select(a, p, p + (r - p - 4) / 5, (r - p - 4) / 10 + 1);
    //按照中位数划分
    int i = Partition(a, p, r, x);
    //求较小数数组的长度
    len = i - p + 1;
    //若较小数数组的长度小于等于k,说明第k小的元素在这个数组内,将其递归
    if (k <= len)
    {
        return Select(a, p, i, k);
    }
    //否则,说明第k小的元素在较大数数组,将其递归
    else
    {
        return Select(a, i + 1, r, k - len);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值