有效地生成唯一的整数对

Posted

技术标签:

【中文标题】有效地生成唯一的整数对【英文标题】:Efficiently generating unique pairs of integers 【发布时间】:2013-03-25 11:06:25 【问题描述】:

在 MATLAB 中,我想生成 n 范围内的随机整数对 [1, m],其中每一对都是唯一的。为了唯一性,我认为这对中数字的顺序无关紧要,因此[3, 10] 等于[10, 3]。 此外,每一对应该由两个不同的整数组成;即[3, 4] 可以,但[3, 3] 会被拒绝。 编辑:每个可能的配对都应该以相同的可能性被选择。

(显然对参数的约束是n <= m(m-1)/2。)

m 很小时,我已经能够成功地做到这一点,就像这样:

m = 500; n = 10;                   % setting parameters

A = ((1:m)'*ones(1, m));           % each column has the numbers 1 -> m
idxs1 = squareform(tril(A', -1))'; 
idxs2 = squareform(tril(A, -1))';   
all_pairs = [idxs1, idxs2];        % this contains all possible pairs

idx_to_use = randperm( size(all_pairs, 1), n );  % choosing random n pairs
pairs = all_pairs(idx_to_use, :)       

pairs =

   254   414
   247   334
   111   146
   207   297
    45   390
   229   411
     9    16
    75   395
    12   338
    25   442

但是,矩阵A 的大小为m x m,这意味着当m 变大(例如超过10,000)时,MATLAB 会耗尽内存。

我考虑生成大量随机数randi(m, [n, 2]),并反复拒绝重复的行,但我担心当n 接近m(m-1)/2 时会陷入循环。

有没有一种更简单、更简洁的方法来生成唯一的不同整数对?

【问题讨论】:

嗯,你有没有试过用unique(round(rand(n+20,2)*m),'rows')生成一个A,测试长度是否至少为n(如果不是,则重复该过程),然后选择第一个@ 987654338@ 行?这可能会慢一些,但值得一试 @jucestain 是的,我确实考虑过这一点——我在倒数第二段中提到了这种方法。我担心的是,如果 n 非常大,算法将不得不重复循环,直到它有效地偶然找到每一对。 【参考方案1】:

如果以正确的方式查看,简单、轻松。

您希望生成 n 对整数 [p,q],使得 p 和 q 位于区间 [1,m] 中,并且 p

有多少可能的配对?对的总数仅为 m*(m-1)/2。 (即从 1 到 m-1 的数字之和。)

所以我们可以在 [1,m*(m-1)/2] 范围内生成 n 个随机整数。 Randperm 很好地做到了这一点。 (旧的 matlab 版本不允许 randperm 的第二个参数。)

k = randperm(m/2*(m-1),n);

(请注意,我以一种有趣的方式用 m 编写了这个表达式,在一个奇怪的地方除以 2。这避免了一些接近上限的 m 值的精度问题。)

现在,如果我们将每个可能的对 [p,q] 与 k 中的一个整数相关联,我们可以从 k 中生成的整数倒推到一对 [p,q]。因此,该列表中的前几对是:

[1,2], [1,3], [2,3], [1,4], [2,4], [3,4], ..., [m-1,m]

我们可以将它们视为大小为 m x m 的严格上三角数组中的元素,即主对角线上方的元素。

q = floor(sqrt(8*(k-1) + 1)/2 + 1/2);
p = k - q.*(q-1)/2;

看到这些公式从 k 中展开的元素中恢复 p 和 q。我们可以说服自己这确实有效,但也许这里的一个简单方法就是这个测试:

k = 1:21;
q = floor(sqrt(8*(k-1) + 1)/2 + 3/2);
p = k - (q-1).*(q-2)/2;
[k;p;q]'

ans =
     1     1     2
     2     1     3
     3     2     3
     4     1     4
     5     2     4
     6     3     4
     7     1     5
     8     2     5
     9     3     5
    10     4     5
    11     1     6
    12     2     6
    13     3     6
    14     4     6
    15     5     6
    16     1     7
    17     2     7
    18     3     7
    19     4     7
    20     5     7
    21     6     7

另一种测试方法是显示所有对都是针对小案例生成的。

m = 5;
n = 10;
k = randperm(m/2*(m-1),n);
q = floor(sqrt(8*(k-1) + 1)/2 + 3/2);
p = k - (q-1).*(q-2)/2;

sortrows([p;q]',[2 1])
ans =
     1     2
     1     3
     2     3
     1     4
     2     4
     3     4
     1     5
     2     5
     3     5
     4     5

是的,看起来一切正常。现在试试 m 和 n 的一些大数来测试所用的时间。

tic
m = 1e6;
n = 100000;
k = randperm(m/2*(m-1),n);
q = floor(sqrt(8*(k-1) + 1)/2 + 3/2);
p = k - (q-1).*(q-2)/2;
toc

Elapsed time is 0.014689 seconds.

该方案适用于大约 1e8 的 m,然后由于双精度中的精度错误而失败。在 m/2*(m-1) 超过 2^53 之前,确切的限制应该是 m 不大于 134217728。一个不错的功能是不需要拒绝重复对。

【讨论】:

非常聪明,做得很好。将随机整数确定性地映射到成对的数字是一个巧妙的技巧。谢谢。 哇!我从来不知道 MATLAB 有这个功能。 +1! 重读一下,我才注意到原来的问题陈述中 [3,3] 会被拒绝。所以我会修改东西。变化很小。【参考方案2】:

这更像是一种通用方法,而不是 matlab 解决方案。

您如何先执行以下操作,然后填充如下所示的向量。

x[n] = rand()
x[n + 1] = x[n] + rand() %% where rand can be equal to 0.

然后你再次执行以下操作

x[n][y] = x[n][y] + rand() + 1

如果

x[n] == x[n+1]

您将确保尚未选择同一对。

完成后,如果您希望矩阵随机分布,您可以在矩阵上运行置换算法。

这种方法将为您提供所有可能性或 2 个整数对,并且它在 O(n) 中运行,其中 n 是矩阵的高度。

【讨论】:

谢谢,这听起来很有希望,但我不太明白这个符号。 x 是向量还是二维矩阵? ny 是什么?谢谢。 所以我来自 C++ 背景。 x[n] 是 n x 1 矩阵,x[n][y] 是 n x y 矩阵,向量是 n x 1 矩阵。 好的,谢谢。我认为,当n 接近m(m-1)/2 时,确保尚未选择每一对的步骤可能会导致速度变慢,如上所述。 实际上它在 O(n) 中运行,因此您只需检查您上方的每个条目一次。因此,对于每个条目,您只需检查 x[n+1] 您不需要检查完整列表。 好的,我现在明白了,谢谢。我认为这会奏效,但我最终希望以相同的概率选择每一对可能的配对;我想这不会这样做?【参考方案3】:

以下代码可以满足您的需要:

n = 10000;
m = 500;
my_list = unique(sort(round(rand(n,2)*m),2),'rows');
my_list = my_list(find((my_list(:,1)==my_list(:,2))==0),:);
%temp = my_list;    %In case you want to check what you initially generated.
while(size(my_list,1)~=n)
    %my_list = unique([my_list;sort(round(rand(1,2)*m),2)],'rows');
    %Changed as per @jucestain's suggestion.
    my_list = unique([my_list;sort(round(rand((n-size(my_list,1)),2)*m),2)],'rows');
    my_list = my_list(find((my_list(:,1)==my_list(:,2))==0),:);
end

【讨论】:

谢谢,这在n << m(m-1)/2 时效果很好,但是当我设置n = m(m-1)/2 - 1 时,这似乎永远运行,而我原来的方法只需要几秒钟。也许我需要两种不同的方法,具体取决于 nm 的值... @BillCheatham:也许是这样。 n 的值越小,m 的值越大,效果越好。 如果我理解正确,这将永远持续下去,因为您只是使用 rand(n,2) 而不是像我在 cmets 中建议的 rand(n+padding,2)。填充将减少必须再次重新计算 my_list 的可能性。之后只需选择 my_list 的前 n 行...这是一种简单的方法,但可能有一种在数学上更合理、更直接的方法来实现它。

以上是关于有效地生成唯一的整数对的主要内容,如果未能解决你的问题,请参考以下文章

如何(有效地)生成不相交的集合,同时只使用一次元素对?

生成唯一且随机的整数

在 0 和 'x' 之间生成唯一的随机数(整数)

有效地使用 *ngFor 和网格 CSS

使用 select last_insert_id 生成唯一 ID

如何在词法分析器生成器中有效地实现最长匹配?