练车加端盘子也挡不住我学习系列——随机选择算法

废话不多说,直接步入正题!

随机问题

如何从一个无序的数组中求出第K大的数?

常规思想

直接选择排序从小到大,然后直接找出第K个数即可。时间复杂度为O(n^2)。总的来说,这不是一个很好的算法!

随机选择算法

随机选择算法的原理类似于随机快速排序算法。当对A[left,right]执行一次randPartition函数之后,主元左侧的元素个数就是确定的,且它们都小于主元。假设此时主元是A[p],那么A[p]就是A[left,right]中的第一个p-left+1大的数。不妨令M表示p-left+1,那么如果k与M相等成立,说明第k大的数就是主元A[p];如果k<M成立,就说明第k大的数在主元右侧,即A[(p+1)…right]中的第k-M大,往右侧递归即可。算法以left与right相等作为递归边界,返回A[left]。可以写出它的伪代码为:

int randPartition(int n[], int left, int right){
    //生成[left,right]内的随机数p
    int p = round(rand() / RAND_MAX*(right - left) + left);
    swap(n[p], n[left]);    //交换n[p],n[left];swap函数在头文件algorithm下
    //以下为原先(快速排序)中的Partition函数的划分过程
    int temp = n[left];     //将n[left]存放在临时变量temp中
    while (left<right)      //只要left和right不相遇
    {
        while (left<right&&n[right]>temp) right--;      
        n[left] = n[right];
        while (left < right&&n[left] <= temp) left++;
        n[right] = n[left];
    }
    n[left] = temp;         //把temp放在left和right相遇的地方
    return left;            //返回相遇下标
}
int randSelect(int A[],int left,int right,int K){
	if(left==right)			//边界 
		return A[left];		//划分后主元位置为p 
	int p=randPartition(A,left,right);		//A[p]是A[left,right]中的第M大 
	int M=p-left+1;		 
	if(K==M)			//找到第K大的数
		return A[p];
	if(K<M)			//第K大的数在主元左侧 
		return randSelect(A,left,p-1,K);
	else			//第K大的数在主元右侧 
		return randSelect(A,p+1,right,K-M);
} 

题目

给定一个由整数组成的集合,集合中的整数各不相同,现在要将它分为两个子集合,使得这两个子集合的并为原集合,交为空,同时在这两个子集合的元素个数n1和n2之差的绝对值尽可能小的情况下,它们各自的元素之和S1和S2之差的绝对值尽可能大。求|S1-S2|的值。


#include<cstdio>
#include<cstdlib>
#include<time.h>
#include<cmath>
#include<algorithm>
using namespace std;
//选取随机主元,对区间[left,right]进行划分
int randPartition(int n[], int left, int right){
    //生成[left,right]内的随机数p
    int p = round(rand() / RAND_MAX*(right - left) + left);
    swap(n[p], n[left]);    //交换n[p],n[left];swap函数在头文件algorithm下
    //以下为原先(快速排序)中的Partition函数的划分过程
    int temp = n[left];     //将n[left]存放在临时变量temp中
    while (left<right)      //只要left和right不相遇
    {
        while (left<right&&n[right]>temp) right--;      
        n[left] = n[right];
        while (left < right&&n[left] <= temp) left++;
        n[right] = n[left];
    }
    n[left] = temp;         //把temp放在left和right相遇的地方
    return left;            //返回相遇下标
}
//随机选择算法,从n[left,right]中找到第k大的数,并进行划分
void randSelect(int n[], int left, int right,int k){
    if (left == right) return;
    int p = randPartition(n, left, right);
    int m = p - left + 1;
    if (k == m) return;
    if (k<m)
    {
        randSelect(n, left, p - 1, k);
    }
    else
    {
        randSelect(n, p + 1, right, k - m);
    }
}
int main(){
    srand((unsigned)time(NULL));        //初始化随机数种子
    int n[] = {1,6,33,18,4,0,10,5,12,7,2,9,3};  
    int sum = 0;
    for (int  i = 0; i < 13; i++)
    {
        sum += n[i];
    }
    randSelect(n, 0, 13 - 1, 13 / 2);
    int sum1 = 0;
    for (int i = 0; i < 13/2; i++)
    {
        sum1 += n[i];//计算前一个集合元素之和
    }
    printf("\n%d\n", (sum - sum1) - sum1);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无所畏惧的man

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值