java唯一随机数_java - 创建没有重复的随机数

本文探讨了在生成不重复随机序列时的效率问题,提出了一种从排序列表中按需插入的方法,以保证序列的唯一性。通过比较不同策略,如直接插入、使用集合和二分搜索,分析了它们的时间复杂性和适用场景。在小规模数据中,简单插入方法表现良好,而随着数据规模增大,采用二分搜索的实现能显著提升性能。此外,还对比了生成所有索引并随机选择删除的低效做法。
摘要由CSDN通过智能技术生成

生成序列的所有索引通常是一个坏主意,因为它可能需要花费很多时间,特别是如果要选择的数字与MAX的比率较低(复杂性由n < rand_num[j] - j主导)。 如果要选择的数字与rand_num[j] - j的比率接近1会变得更糟,因为从所有序列中移除所选择的指数也变得昂贵(我们接近rand_num[j])。 但对于小数字,这通常效果很好,并不是特别容易出错。

通过使用集合过滤生成的索引也是一个坏主意,因为花费一些时间将索引插入到序列中,并且无法保证进度,因为可以多次绘制相同的随机数(但是对于足够大的MAX它是不太可能)。 这可能接近复杂性

n < rand_num[j] - j,忽略重复项并假设集合使用树进行有效查找(但是具有分配树节点且可能必须重新平衡的显着恒定成本rand_num[j] - j)。

另一种选择是从一开始就唯一地生成随机值,保证正在取得进展。 这意味着在第一轮中,生成MAX中的随机索引:

items i0 i1 i2 i3 i4 i5 i6 (total 7 items)

idx 0 ^^ (index 2)

在第二轮中,仅生成MAX(因为已选择一个项目):

items i0 i1 i3 i4 i5 i6 (total 6 items)

idx 1 ^^ (index 2 out of these 6, but 3 out of the original 7)

然后需要调整指数的值:如果第二个指数落在序列的后半部分(在第一个指数之后),则需要递增以考虑差距。 我们可以将其实现为循环,允许我们选择任意数量的唯一项。

对于短序列,这是非常快的MAX算法:

void RandomUniqueSequence(std::vector &rand_num,

const size_t n_select_num, const size_t n_item_num)

{

assert(n_select_num <= n_item_num);

rand_num.clear(); // !!

// b1: 3187.000 msec (the fastest)

// b2: 3734.000 msec

for(size_t i = 0; i < n_select_num; ++ i) {

int n = n_Rand(n_item_num - i - 1);

// get a random number

size_t n_where = i;

for(size_t j = 0; j < i; ++ j) {

if(n + j < rand_num[j]) {

n_where = j;

break;

}

}

// see where it should be inserted

rand_num.insert(rand_num.begin() + n_where, 1, n + n_where);

// insert it in the list, maintain a sorted sequence

}

// tier 1 - use comparison with offset instead of increment

}

其中MAX是您的5和n < rand_num[j] - j是您的rand_num[j] - j. rand_num[j]在rand_num(含)中返回随机整数。 如果通过使用二分搜索来找到插入点来选择许多项(例如,不是5而不是500),则可以使这更快一些。 为此,我们需要确保满足要求。

我们将使用比较MAX进行二进制搜索

n < rand_num[j] - j.我们需要显示rand_num[j] - j仍然是排序序列rand_num[j]的排序序列。幸运的是很容易显示,因为原始rand_num的两个元素之间的最小距离是一个(生成的数字是唯一的,所以总是存在差异 至少1)。 同时,如果我们从所有元素中减去指数j

rand_num[j],索引的差异正好是1.因此在“最差”的情况下,我们得到一个恒定的序列 - 但从不减少。 因此可以使用二进制搜索,产生O(n log(n))算法:

struct TNeedle { // in the comparison operator we need to make clear which argument is the needle and which is already in the list; we do that using the type system.

int n;

TNeedle(int _n)

:n(_n)

{}

};

class CCompareWithOffset { // custom comparison "n < rand_num[j] - j"

protected:

std::vector::iterator m_p_begin_it;

public:

CCompareWithOffset(std::vector::iterator p_begin_it)

:m_p_begin_it(p_begin_it)

{}

bool operator ()(const int &r_value, TNeedle n) const

{

size_t n_index = &r_value - &*m_p_begin_it;

// calculate index in the array

return r_value < n.n + n_index; // or r_value - n_index < n.n

}

bool operator ()(TNeedle n, const int &r_value) const

{

size_t n_index = &r_value - &*m_p_begin_it;

// calculate index in the array

return n.n + n_index < r_value; // or n.n < r_value - n_index

}

};

最后:

void RandomUniqueSequence(std::vector &rand_num,

const size_t n_select_num, const size_t n_item_num)

{

assert(n_select_num <= n_item_num);

rand_num.clear(); // !!

// b1: 3578.000 msec

// b2: 1703.000 msec (the fastest)

for(size_t i = 0; i < n_select_num; ++ i) {

int n = n_Rand(n_item_num - i - 1);

// get a random number

std::vector::iterator p_where_it = std::upper_bound(rand_num.begin(), rand_num.end(),

TNeedle(n), CCompareWithOffset(rand_num.begin()));

// see where it should be inserted

rand_num.insert(p_where_it, 1, n + p_where_it - rand_num.begin());

// insert it in the list, maintain a sorted sequence

}

// tier 4 - use binary search

}

我在三个基准测试中对此进行了测试。 首先,从7个项目中选择3个数字,所选项目的直方图累计超过10,000次:

4265 4229 4351 4267 4267 4364 4257

这表明7个项目中的每一个被选择大致相同的次数,并且没有由算法引起的明显偏差。 还检查所有序列的正确性(内容的唯一性)。

第二个基准涉及从5000个项目中选择7个数字。 算法的几个版本的时间累计超过10,000,000次运行。 结果在代码中的注释中表示为MAX.算法的简单版本稍微快一些。

第三个基准涉及从5000个项目中选择700个数字。 再次积累了几个版本的算法的时间,这次超过10,000次运行。 结果在代码中的注释中表示为MAX.算法的二进制搜索版本现在比简单版本快两倍以上。

第二种方法开始在我的机器上选择超过cca 75项目的速度更快(注意,任一算法的复杂性不依赖于项目数量,MAX)。

值得一提的是,上述算法按升序生成随机数。 但是添加另一个数组会很简单,数字将按生成顺序保存,然后返回(以可忽略的额外成本set)。 没有必要改变输出:这会慢得多。

请注意,源代码是用C ++编写的,我的机器上没有Java,但概念应该是清楚的。

编辑:

为了娱乐,我还实现了生成包含所有索引的列表的方法

set,随机选择它们并从列表中删除它们以保证唯一性。 由于我选择了相当高的b2(5000),性能是灾难性的:

// b1: 519515.000 msec

// b2: 20312.000 msec

std::vector all_numbers(n_item_num);

std::iota(all_numbers.begin(), all_numbers.end(), 0);

// generate all the numbers

for(size_t i = 0; i < n_number_num; ++ i) {

assert(all_numbers.size() == n_item_num - i);

int n = n_Rand(n_item_num - i - 1);

// get a random number

rand_num.push_back(all_numbers[n]); // put it in the output list

all_numbers.erase(all_numbers.begin() + n); // erase it from the input

}

// generate random numbers

我还用set(一个C ++集合)实现了这个方法,实际上它在基准测试b2上排名第二,比二进制搜索的方法慢了大约50%。 这是可以理解的,因为set使用二叉树,其插入成本类似于二进制搜索。 唯一的区别是获得重复项目的机会,这会减慢进度。

// b1: 20250.000 msec

// b2: 2296.000 msec

std::set numbers;

while(numbers.size() < n_number_num)

numbers.insert(n_Rand(n_item_num - 1)); // might have duplicates here

// generate unique random numbers

rand_num.resize(numbers.size());

std::copy(numbers.begin(), numbers.end(), rand_num.begin());

// copy the numbers from a set to a vector

完整的源代码在这里。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值