随机选择算法

2023/04/01 程序设计 共 3686 字,约 11 分钟

上机实践报告

一、目的

1.熟悉算法设计的基本思想

2.掌握随机选择算法(Randomized Selection)

二、内容与设计思想

  1. 用oj随机生成的1000000~4500000的数据规模,使用随机选择算法找到中位数并数输出,并画图描述不同规模下的运行时间差异;

  2. (选做)使用SELECT算法重复第一步,并介绍自己的思路以及两个算法的区别;(做出加分)

三、使用环境

推荐使用C/C++集成编译环境。

水杉id:10205501403刘佳凡

四、实验过程

源代码

Randomized-select

#include <bits/stdc++.h>
using namespace std;

class Random
{
public:
    //随机化分割
    int randomized_partition(vector<int> &a, int p, int r);
    int randomized_select(vector<int> &a, int p, int r, int i);
};

//下标为[p, r]之间的元素
int Random::randomized_partition(vector<int> &a, int p, int r)
{
    int q = rand() % (r - p + 1) + p; //就是说先随机选一个--在p-r之间选一个包含p r
    int temp = a[q];
    a[q] = a[r];
    a[r] = temp;
    int i = p - 1;
    for (int j = p; j < r; j++)
    {
        if (a[j] < a[r])
        {
            i = i + 1;
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
    }
    temp = a[i + 1];
    a[i + 1] = a[r];
    a[r] = temp;
    return i + 1;
}

//迭代版本
int Random::randomized_select(vector<int> &a, int p, int r, int i)
{
    if (p == r)
        return a[p];
    int q;
    q = randomized_partition(a, p, r);//用来划分的那个数的下标
    int k;
    k = q - p + 1;//可以得到用来划分的那个数是第几大的
    if (i == k)
        return a[q];
    else if (i < k)
        return randomized_select(a, p, q - 1, i);
    else
        return randomized_select(a, q + 1, r, i - k);
}

int main()
{
    Random c;
    vector<int> a;
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int b;
        cin >> b;
        a.push_back(b);
        //cout<<a[i]<<endl;
    }
    cout << c.randomized_select(a, 0, n - 1, n / 2);
}

Select

#include<bits/stdc++.h>
using namespace std;

class middle
{
    public:
    void insert_sort(int array[], int left, int a);
    int find_middle(int array[], int left, int right);
    int find_index(int array[], int left, int right, int median);
    int Select(int array[], int left, int right, int k);

    private:
    int middle_array[900000];//用一个中位数数组来储存产生的中位数

};

void middle::insert_sort(int array[], int left, int a)//left是数组开始的下标,a代表了数组还有几个元素
{
    for (int j = left; j < left + a; j++)
    {
        int key = array[j];
        int i = j - 1;
        while (i > left && array[i] > key)
        {
            array[i + 1] = array[i];
            i--;
        }
        array[i + 1] = key;
    }
}

int middle::find_middle(int array[], int left, int right)
{
    if (left == right)
        return array[left];

    int index;
    for (index = left; index < right - 5; index += 5)
    {
        insert_sort(array, index, 4);
        int num = index - left;
        middle_array[num / 5] = array[index + 2];
    }

    // 处理剩余元素
    int remain = right - index + 1;
    if (remain > 0)
    {
        insert_sort(array, index, remain - 1);
        int num = index - left;
        middle_array[num / 5] = array[index + remain / 2];
    }

    //得到新数组的下标
    int new_array = (right - left) / 5 - 1;
    if ((right - left) % 5 != 0)
        new_array++;

    // 根据剩余条件来看是否递归
    if (new_array == 0)
        return middle_array[0];
    else
        return find_middle(middle_array, 0, new_array);
}

// 寻找中位数的所在位置
int middle::find_index(int array[], int left, int right, int median)
{
    for (int i = left; i <= right; i++)
    {
        if (array[i] == median)
            return i;
    }
    return -1;
}

int middle::Select(int array[], int left, int right, int k)
{
    // 找到所有数的中位数
    int median = find_middle(array, left, right);
    // 将中位数的中位数与最右元素交换
    int index = find_index(array, left, right, median);
    swap(array[index], array[right]);
    //partition
    int i = left - 1;
    for (int j = left; j < right; j++)
    {
        if (array[j] < array[right])
        {
            i = i + 1;
            swap(array[i], array[j]);
        }
    }
    swap(array[i + 1], array[right]);
    //划分之后寻找
    i = i + 1;
    int m = i - left + 1;
    if (m == k)
        return array[i];
    else if (m > k)
        return Select(array, left, i - 1, k);
    else
        return Select(array, i + 1, right, k - m);
}

int main()
{
    middle c;
    int n;
    cin>>n;
    int array[n];
    for(int i =0;i<n;i++)
    {
        cin>>array[i];
    }
    cout << c.Select(array, 0, n- 1, n/2 )<< endl;
    return 0;
}

画图描述不同时间下不同规模的差异

image-20211102152412623

五、总结

关于select算法思路介绍:

​ 1.先将数组划分为每五个元素一组的小数组,然后分别对每个小数组进行插入排序,找出其中位数。

​ 2.将每个小数组得到的中位数存入一个新的数组中,然后再对这个新数组进行第一步的操作,直到只剩下一个元素,也就找到了中位数的中位数。

​ 3.然后将找到的这个数与原数组最右边的元素交换,也就是说以这个数作为划分标准,剩下的做法与Randomized-select算法相同。

两个算法的不同点:

​ Randomized-select划分时是以产生的随机数进行划分,也就是等概率地返回任何元素作为主元;而select算法也是来自快速排序时的确定性划分算法partition,但做了修改在于,也就是说把划分的主元也作为了输入参数,他的主元是被划分的每个数组的中位数的中位数。这样修改的好处在于,能够保证得到对数组的一个好的划分 。通过上次纸质作业和教材上的理论,再加上这次实验结果,可以得知 Randomized-select的平均时间复杂度为 [公式],但是,如果考虑最坏情况,即每次选取的种子点都为当时序列的最大值或最小值,那么,最终的时间复杂度是 [公式] ,这是需要避免的一种情况。Select的最坏运行时间为T(n)。所以可以看到图里两种算法的运行时间是相差不大的。

关于这次实验过程心得

​ 首先是更加熟练地掌握了两种算法的应用,但是在写select算法时,问题出在对找中位数的中位数那块,一开始之间返回了中位数那个数组的中间值,忘记了这个数组也是无序,所以得一直递归直到只剩一个元素。

文档信息

Search

    Table of Contents