数组保持不变的概率是多少?
Posted
技术标签:
【中文标题】数组保持不变的概率是多少?【英文标题】:What is the probability that the array will remain the same? 【发布时间】:2012-08-06 00:22:57 【问题描述】:这个问题在微软面试中被问到。非常想知道为什么这些人会问这么奇怪的概率问题?
给定一个 rand(N),一个随机生成器,它生成从 0 到 N-1 的随机数。
int A[N]; // An array of size N
for(i = 0; i < N; i++)
int m = rand(N);
int n = rand(N);
swap(A[m],A[n]);
编辑:注意种子不是固定的。
数组 A 保持不变的概率是多少? 假设数组包含唯一元素。
【问题讨论】:
对于固定的N
和固定的种子,概率要么是0
要么是1
,因为它根本不是随机的。
如果这确实是一个 C 程序(如标签所示),概率为 1。数组元素按值传递(C 中没有按引用传递),所以 swap
函数不可能改变A
的内容。 \edit: 好吧,swap
也可以是一个宏……希望它不是 :)
@reima:它可能是一个宏。
考虑在 math.stackexchange.com 上提问。改写:给定随机 a_i,b_i,排列 (a_1 b_1) (a_2 b_2) ... (a_n b_n) = 对称群 S_n 中的同一性的概率是多少?
呃...没有人知道 A 从未初始化?
【参考方案1】:
一个简单的解决方案是
p >= 1 / NN
由于数组保持不变的一种可能方式是每次迭代都使用m = n
。并且m
等于n
有可能1 / N
。
肯定比这更高。问题是多少..
第二个想法:人们也可以争辩说,如果你随机打乱一个数组,每个排列都有相等的概率。由于存在n!
排列,因此只得到一个(我们一开始就有的)的概率是
p = 1 / N!
这比之前的结果好一点。
如前所述,该算法是有偏见的。这意味着并非每个排列都具有相同的概率。所以1 / N!
不太准确。您必须找出排列的分布情况。
【讨论】:
数组随机洗牌,问题是是否均匀洗牌。这两个概念无关。 @DietrichEpp:是的,那么你必须证明这个算法是否有偏见。如果它没有偏差,则它是均匀分布的。 啊,我只记得一个超级简单的证明算法是有偏见的。该算法可以以 N^N 种不同的方式统一执行,但排列的数量为 N!几乎可以肯定不是 N^N 的除数,因此,算法偏向于 N >= 3。 @duedl0r:这是一个数学证明,因此您可以通过检查逻辑步骤并确保它们正确遵循来验证它。如果您已经知道答案是什么,则无需实际运行该程序。正如我所说,这不是一个建设性的证明,所以这个证明没有给你任何关于有多少偏见的信息,它只是证明必须有偏见,否则就会有逻辑上的矛盾。当您说它“取消”时,这实际上只是一个猜测,但欢迎您尝试证明它确实取消了。【参考方案2】:每次迭代中 m==n 的概率,然后执行 N 次。 P(m==n) = 1/N。所以我认为这种情况下 P=1/(n^2) 。但是随后您必须考虑将值交换回来。所以我认为答案是(文本编辑器让我)1/N^N。
【讨论】:
绝对高于(1/N)^N
。问题是,这本身就是数组从未真正改变的概率,我们还没有计算事物移动并恰好返回到原始状态的概率。【参考方案3】:
仅供参考,不确定上述界限 (1/n^2) 是否成立:
N=5 -> 0.019648 < 1/25
N=6 -> 0.005716 < 1/36
采样代码:
import random
def sample(times,n):
count = 0;
for i in range(times):
count += p(n)
return count*1.0/times;
def p(n):
perm = range(n);
for i in range(n):
a = random.randrange(n)
b = random.randrange(n)
perm[a],perm[b]=perm[b],perm[a];
return perm==range(n)
print sample(500000,5)
【讨论】:
注意你可以通过 perm[a], perm[b] = perm[b], perm[a] 进行交换【参考方案4】:非常想知道为什么这些人会提出如此奇怪的概率问题?
问这样的问题是因为他们可以让面试官深入了解受访者的
能够阅读代码(非常简单的代码,但至少是一些东西) 能够分析算法以识别执行路径 运用逻辑找出可能的结果和极端情况的技能 解决问题时的推理和解决问题的能力 沟通和工作技能 - 他们是提出问题,还是根据手头的信息孤立地工作... 等等。提出暴露受访者这些属性的问题的关键是要有一段看似简单的代码。这摆脱了非编码人员被卡住的冒名顶替者;傲慢的人跳到错误的结论;懒惰或低于标准的计算机科学家找到了一个简单的解决方案并停止寻找。通常,正如他们所说,重要的不是你是否得到了正确的答案,而是你是否对自己的思维过程印象深刻。
我也会尝试回答这个问题。在一次采访中,我会解释自己而不是提供单行的书面答案——这是因为即使我的“答案”是错误的,我也能够展示出逻辑思维。
A 将保持不变 - 即相同位置的元素 - 当
m == n
在每次迭代中(这样每个元素只与自身交换);或
任何被交换的元素都会被交换回原来的位置
第一种情况是 duedl0r 给出的“简单”情况,即数组未更改的情况。这可能是答案,因为
数组 A 保持不变的概率是多少?
如果数组在i = 1
处发生更改,然后在i = 2
处恢复,则它处于原始状态,但并未“保持不变” - 它已更改,然后又更改回来。这可能是一个聪明的技术问题。
然后考虑元素被交换和交换回来的机会 - 我认为在采访中计算超出了我的头脑。显而易见的考虑是,这不需要进行更改 - 更改回交换,可以很容易地在三个元素之间进行交换,交换 1 和 2,然后交换 2 和 3,交换 1 和 3,最后交换 2 和 3。继续,可能会有 4、5 个或更多像这样的“圆形”项目之间的交换。
实际上,与其考虑数组不变的情况,不如考虑它改变的情况可能更简单。考虑一下这个问题是否可以映射到像Pascal's triangle这样的已知结构上。
这是一个难题。我同意在面试中很难解决,但这并不意味着在面试中问太难。差的候选人不会有答案,一般的候选人会猜测显而易见的答案,而优秀的候选人会解释为什么这个问题太难回答了。
我认为这是一个“开放式”问题,可以让面试官深入了解候选人。出于这个原因,即使在面试中很难解决,在面试中提出这个问题也是一个好的问题。提出问题不仅仅是检查答案是对还是错。
【讨论】:
更长的周期呢?他们可以不存在吗?还是随着周期长度的增加它们越来越不可能?人们希望这两种情况中的一种是真实的,并且最好对它们的重要性进行某种定量估计。 不幸的是它不是那样的......当你交换 1 和 2,然后是 2 和 3,然后是 1 和 3 它不会是第一个状态......后一个需要再次被交换...... 对,但这并不能证明没有超过 2 次交换的序列是可能的。例如,在您的论点中,四次交换使一切正常。 @andrewcooke 可以存在更长的周期,但会假设它们的长度可能呈指数下降。大概会有某种限制。 哦,对不起,维克多,以为你在跟我说话!【参考方案5】:这不是一个完整的解决方案,但至少是这样。
进行一组没有效果的特定交换。我们知道一定是它的交换最终形成了一堆不同大小的循环,总共使用了n
交换。 (为此,没有效果的交换可以被认为是大小为 1 的循环)
也许我们可以
1) 根据循环的大小将它们分成组 2) 计算得到每组的方法数。
(主要问题是有 吨 个不同的组,但如果不考虑不同的分组,我不确定你会如何实际计算。)
【讨论】:
【参考方案6】:下面是 C 代码,用于计算 rand 可以产生的 2N 个索引元组的值的数量并计算概率。从 N = 0 开始,它显示计数为 1、1、8、135、4480、189125 和 12450816,概率为 1、1、0.5、0.185185、0.0683594、0.0193664 和 0.00571983。计数没有出现在Encyclopedia of Integer Sequences 中,所以要么我的程序有错误,要么这是一个非常模糊的问题。如果是这样,这个问题并不是要由求职者来解决,而是要暴露他们的一些思维过程以及他们如何处理挫折。我不会认为这是一个很好的面试问题。
#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#define swap(a, b) do int t = (a); (a) = (b); (b) = t; while (0)
static uint64_t count(int n)
// Initialize count of how many times the original order is the result.
uint64_t c = 0;
// Allocate space for selectors and initialize them to zero.
int *r = calloc(2*n, sizeof *r);
// Allocate space for array to be swapped.
int *A = malloc(n * sizeof *A);
if (!A || !r)
fprintf(stderr, "Out of memory.\n");
exit(EXIT_FAILURE);
// Iterate through all values of selectors.
while (1)
// Initialize A to show original order.
for (int i = 0; i < n; ++i)
A[i] = i;
// Test current selector values by executing the swap sequence.
for (int i = 0; i < 2*n; i += 2)
int m = r[i+0];
int n = r[i+1];
swap(A[m], A[n]);
// If array is in original order, increment counter.
++c; // Assume all elements are in place.
for (int i = 0; i < n; ++i)
if (A[i] != i)
// If any element is out of place, cancel assumption and exit.
--c;
break;
// Increment the selectors, odometer style.
int i;
for (i = 0; i < 2*n; ++i)
// Stop when a selector increases without wrapping.
if (++r[i] < n)
break;
else
// Wrap this selector to zero and continue.
r[i] = 0;
// Exit the routine when the last selector wraps.
if (2*n <= i)
free(A);
free(r);
return c;
int main(void)
for (int n = 0; n < 7; ++n)
uint64_t c = count(n);
printf("N = %d: %" PRId64 " times, %g probabilty.\n",
n, c, c/pow(n, 2*n));
return 0;
【讨论】:
一个独立的 Python 程序给出了相同的结果,直到 N=6,所以你的实现是正确的。每个计数都可以除以 N^3,但这也不会出现在 OEIS 中。【参考方案7】:很容易观察界限 1/nn
这是显示反指数上限的不完整想法。
您从 1,2,..,n 中抽取了 2n 次数字。如果其中任何一个是唯一的(仅出现一次),则数组肯定会改变,因为元素已经消失并且无法返回其原始位置。
一个固定数唯一的概率是 2n * 1/n * (1-1/n)^(2n-1)=2 * (1-1/n)^(2n-1) 这是渐近的2/e2,从 0 开始。[2n 因为你选择在哪个尝试中获得它,1/n 你在那次尝试中获得它,(1-1/n)^(2n -1) 你在其他尝试中没有得到它]
如果事件是独立的,那么所有数字都不唯一的机会是 (2/e2)^n,这意味着 p 2)^n)。不幸的是,它们不是独立的。我觉得可以通过更复杂的分析来显示界限。关键字是“balls and bins problem”。
【讨论】:
【参考方案8】:嗯,我玩这个有点开心。当我第一次阅读这个问题时,我首先想到的是群论(尤其是对称群 Sn)。 for 循环通过在每次迭代中组合转置(即交换)来简单地在 Sn 中构建一个置换 σ。我的数学并不是那么出色,而且我有点生疏,所以如果我的符号不合适,请忍耐。
概述
设A
是我们的数组在排列后不变的事件。我们最终被要求找到事件A
、Pr(A)
的概率。
我的解决方案尝试遵循以下程序:
-
考虑所有可能的排列(即我们的数组的重新排序)
根据这些排列所包含的所谓身份转置的数量,将这些排列分成不相交组。这有助于将问题减少到 even 排列。
确定在给定排列是偶数(并且具有特定长度)的情况下获得恒等排列的概率。
对这些概率求和以获得数组未发生变化的总体概率。
1) 可能的结果
请注意,for 循环的每次迭代都会创建一个交换(或 转置),它会导致以下两种情况之一(但绝不会两者兼而有之):
-
交换了两个元素。
元素与自身交换。出于我们的意图和目的,数组没有改变。
我们标记第二种情况。让我们定义一个 identity transposition 如下:
恒等式转置发生在数字与自身交换时。 也就是上面for循环中n == m的时候。
对于列出的代码的任何给定运行,我们编写N
转置。在这个“链”中可以有0, 1, 2, ... , N
的身份转置。
例如,考虑N = 3
案例:
Given our input [0, 1, 2].
Swap (0 1) and get [1, 0, 2].
Swap (1 1) and get [1, 0, 2]. ** Here is an identity **
Swap (2 2) and get [1, 0, 2]. ** And another **
请注意,有奇数个非恒等转置 (1) 并且数组已更改。
2) 基于身份转置次数的分区
让K_i
成为i
身份转置出现在给定排列中的事件。请注意,这形成了所有可能结果的详尽划分:
0
和N
之间进行身份转置。
因此我们可以申请Law of Total Probability:
现在我们终于可以利用分区了。请注意,当 non-identity 转置的数量是奇数时,数组不可能保持不变*。因此:
*根据群论,排列是偶数或奇数,但绝不是两者。因此奇排列不可能是恒等排列(因为恒等排列是偶排列)。
3) 确定概率
所以我们现在必须确定N-i
even 的两个概率:
第一学期
第一项 表示获得具有 i
恒等转置的排列的概率。事实证明这是二项式的,因为对于 for 循环的每次迭代:
1/N
。
因此对于N
试验,获得i
身份转置的概率为:
第二项
因此,如果您已经做到了这一点,我们已经将问题减少到找到 甚至 N - i
。这表示在给定i
的转置是身份的情况下获得身份置换的概率。我使用一种简单的计数方法来确定在可能的排列数量上实现恒等排列的方法的数量。
首先考虑排列 (n, m)
和 (m, n)
等效。然后,让M
成为可能的非身份排列的数量。我们会经常使用这个数量。
这里的目标是确定可以组合转置集合以形成恒等排列的方式数量。我将尝试根据N = 4
的示例构建通用解决方案。
让我们考虑具有所有身份转置的N = 4
情况(即 i = N = 4
)。让X
表示一个身份转置。对于每个X
,都有N
的可能性(它们是:n = m = 0, 1, 2, ... , N - 1
)。因此,有N^i = 4^4
实现身份置换的可能性。为了完整起见,我们添加了二项式系数C(N, i)
,以考虑恒等转置的排序(这里它正好等于 1)。我试图用上面元素的物理布局和下面的可能性数量来描述这一点:
I = _X_ _X_ _X_ _X_
N * N * N * N * C(4, 4) => N^N * C(N, N) possibilities
现在不用明确替换N = 4
和i = 4
,我们可以看一下一般情况。结合上述与之前找到的分母,我们发现:
这很直观。事实上,1
以外的任何其他值都可能会引起您的警觉。想一想:给定的情况是,所有N
转置都被称为身份。在这种情况下,数组可能没有变化?显然,1
。
现在,再次对于 N = 4
,让我们考虑 2 个身份转置(即 i = N - 2 = 2
)。作为约定,我们将把这两个身份放在最后(并考虑稍后的排序)。我们现在知道我们需要选择两个转置,当它们组合时,它们将成为恒等排列。让我们将任何元素放在第一个位置,称之为t1
。如上所述,有M
的可能性假设t1
不是一个身份(它不可能是因为我们已经放置了两个)。
I = _t1_ ___ _X_ _X_
M * ? * N * N
唯一可能出现在第二个位置的元素是t1
的逆元素,实际上是t1
(这是唯一一个唯一的逆元素)。我们再次包括二项式系数:在这种情况下,我们有 4 个开放位置,我们正在寻找放置 2 个身份排列。我们有多少种方法可以做到这一点? 4选2。
I = _t1_ _t1_ _X_ _X_
M * 1 * N * N * C(4, 2) => C(N, N-2) * M * N^(N-2) possibilities
再看一般情况,这都对应:
最后,我们执行N = 4
没有身份转换的情况(即 i = N - 4 = 0
)。由于有很多可能性,它开始变得棘手,我们必须小心不要重复计算。我们以类似的方式开始,在第一个位置放置一个元素并计算出可能的组合。先取最简单的:相同的换位 4 次。
I = _t1_ _t1_ _t1_ _t1_
M * 1 * 1 * 1 => M possibilities
现在让我们考虑两个独特的元素t1
和t2
。 t1
有 M
的可能性,而 t2
只有 M-1
的可能性(因为 t2
不能等于 t1
)。如果我们用尽所有安排,我们会得到以下模式:
I = _t1_ _t1_ _t2_ _t2_
M * 1 * M-1 * 1 => M * (M - 1) possibilities (1)st
= _t1_ _t2_ _t1_ _t2_
M * M-1 * 1 * 1 => M * (M - 1) possibilities (2)nd
= _t1_ _t2_ _t2_ _t1_
M * M-1 * 1 * 1 => M * (M - 1) possibilities (3)rd
现在让我们考虑三个独特的元素,t1
、t2
、t3
。让我们先放置t1
,然后再放置t2
。像往常一样,我们有:
I = _t1_ _t2_ ___ ___
M * ? * ? * ?
我们还不能说有多少可能的t2
s,我们马上就会知道为什么。
我们现在将t1
放在第三位。请注意,t1
必须去那里,因为如果要在最后一个位置去,我们只会重新创建上面的 (3)rd
安排。重复计算是不好的!这将第三个唯一元素 t3
留在最终位置。
I = _t1_ _t2_ _t1_ _t3_
M * ? * 1 * ?
那么为什么我们要花一点时间来更仔细地考虑t2
s 的数量呢? t1
和 t2
的转置 不能 是不相交的排列(ie 它们必须共享一个(并且只有一个,因为它们也不能相等) 他们的n
或m
)。这样做的原因是因为如果它们不相交,我们可以交换排列的顺序。这意味着我们将重复计算(1)st
排列。
说t1 = (n, m)
。对于某些x
和y
,t2
必须采用(n, x)
或(y, m)
的形式,以便不相交。请注意,x
可能不是n
或m
和y
,很多都不是n
或m
。因此,t2
可能的排列数量实际上是2 * (N - 2)
。
所以,回到我们的布局:
I = _t1_ _t2_ _t1_ _t3_
M * 2(N-2) * 1 * ?
现在t3
必须是t1 t2 t1
组合的倒数。让我们手动完成:
(n, m)(n, x)(n, m) = (m, x)
因此t3
必须是(m, x)
。请注意,这不与t1
不相交且不等于t1
或t2
,因此这种情况不会重复计算。
I = _t1_ _t2_ _t1_ _t3_
M * 2(N-2) * 1 * 1 => M * 2(N - 2) possibilities
最后,将所有这些放在一起:
4) 将它们放在一起
就是这样。向后工作,将我们发现的内容代入步骤 2 中给出的原始总和。我计算了下面N = 4
案例的答案。它与另一个答案中找到的经验数字非常接近!
正确性
如果能在这里应用群论的结果,那就太酷了——也许有!它肯定会帮助让所有这些繁琐的计数完全消失(并将问题简化为更优雅的东西)。我停止在N = 4
工作。对于N > 5
,给出的只是一个近似值(有多好,我不确定)。如果您考虑一下,就会很清楚为什么会这样:例如,给定N = 8
换位,显然有多种方法可以使用上面未说明的四个独特元素来创建身份。随着排列变长(据我所知......),方式的数量似乎变得越来越难以计数。
无论如何,我绝对不能在采访范围内做这样的事情。如果我幸运的话,我会走到分母一步。除此之外,它似乎很讨厌。
【讨论】:
我不认为你是对的,因为事件之间存在依赖关系 - Pr(A and K = 0) + Pr(A and K = 1) + ... + Pr(A and K = N)。根据您的分析,您认为不会。 嗯,谢谢你的注意。也许我应用它不正确,但我正在考虑结果:让 K_1, K_2, ..., K_N 成为我们空间 S 的一个分区。让 A 成为 S 的一个子空间。然后(有时称为总概率,我认为): P(A) = Pr(A | K_i) * P(K_i) 之和。我很确定以这种方式划分也会创建一个分区:没有排列可以在两个不同的子集中(因为它有 single 个身份)并且所有可能的排列都被考虑在内。跨度> 按照你所拥有的就好(除了分子突然出现......) 当然,在这方面答案很弱。我试图通过这个例子给出一些见解,但不可否认它不是很好。它主要是尽可能地尝试有效地计数和计数(通过使用可能发生的模式)。我检查了一些情况,但显然不是全部;那里可能应该有一个星号。如果有一种计算概率的好方法,我认为这个解决方案会非常简洁。但它肯定躲过了我! :) 这是正确的答案。它击中了所有正确的流行词(“permutation”、“transposition”、“identity”)并且推理是正确的:1)考虑实际转置的数量,2)考虑你需要偶数转置和 3)计算所有构成恒等排列的排列对的数量。但是有一个批评:如果您以更简洁的方式编写内容,那么您可能会更明显地做对了。【参考方案9】:问题:数组 A 保持不变的概率是多少? 条件:假设数组包含唯一元素。
尝试了 Java 中的解决方案。
随机交换发生在原始 int 数组上。在 java 方法中,参数总是按值传递,所以在 swap 方法中发生的事情并不重要,因为数组的 a[m] 和 a[n] 元素(来自下面的代码 swap(a[m], a[n]) )是传递的不是完整的数组。
答案是数组将保持不变。尽管有上述情况。参见下面的java代码示例:
import java.util.Random;
public class ArrayTrick
int a[] = new int[10];
Random random = new Random();
public void swap(int i, int j)
int temp = i;
i = j;
j = temp;
public void fillArray()
System.out.println("Filling array: ");
for (int index = 0; index < a.length; index++)
a[index] = random.nextInt(a.length);
public void swapArray()
System.out.println("Swapping array: ");
for (int index = 0; index < a.length; index++)
int m = random.nextInt(a.length);
int n = random.nextInt(a.length);
swap(a[m], a[n]);
public void printArray()
System.out.println("Printing array: ");
for (int index = 0; index < a.length; index++)
System.out.print(" " + a[index]);
System.out.println();
public static void main(String[] args)
ArrayTrick at = new ArrayTrick();
at.fillArray();
at.printArray();
at.swapArray();
at.printArray();
样本输出:
填充数组: 打印数组: 3 1 1 4 9 7 9 5 9 5 交换数组: 打印数组: 3 1 1 4 9 7 9 5 9 5
【讨论】:
swap(i,j) 不可能影响任何事情,因为整数是按值传递的 这就是我的意思。 swap() 方法不会产生任何影响。 只因为你选择了swap
的语言和实现【参考方案10】:
有趣的问题。
我认为答案是 1/N,但我没有任何证据。当我找到证明时,我会编辑我的答案。
到目前为止我得到了什么:
如果 m == n,则不会更改数组。 得到 m == n 的概率是 1/N,因为有 N^2 个选项,只有 N 是合适的((i,i) for each 0
因此,我们得到 N/N^2 = 1/N。
表示在 k 次交换迭代后,大小为 N 的数组保持不变的概率。
P1 = 1/N。 (如下所示)
P2 = (1/N)P1 + (N-1/N)(2/N^2) = 1/N^2 + 2(N-1) / N^3 .
Explanation for P2:
We want to calculate the probability that after 2 iterations, the array with
N elements won't change. We have 2 options :
- in the 2 iteration we got m == n (Probability of 1/N)
- in the 2 iteration we got m != n (Probability of N-1/N)
If m == n, we need that the array will remain after the 1 iteration = P1.
If m != n, we need that in the 1 iteration to choose the same n and m
(order is not important). So we get 2/N^2.
Because those events are independent we get - P2 = (1/N)*P1 + (N-1/N)*(2/N^2).
Pk = (1/N)*Pk-1 + (N-1/N)*X。 (第一个代表 m == n,第二个代表 m != n)
我必须更多地考虑 X 等于什么。 (X 只是真实公式的替代品,不是常数或其他任何东西)
Example for N = 2.
All possible swaps:
(1 1 | 1 1),(1 1 | 1 2),(1 1 | 2 1),(1 1 | 2 2),(1 2 | 1 1),(1 2 | 1 2)
(1 2 | 2 1),(1 2 | 2 2),(2 1 | 1 1),(2 1 | 1 2),(2 1 | 2 1),(2 1 | 2 2)
(2 2 | 1 1),(2 2 | 1 2),(2 2 | 2 1),(2 1 | 1 1).
Total = 16. Exactly 8 of them remain the array the same.
Thus, for N = 2, the answer is 1/2.
编辑: 我想介绍另一种方法:
我们可以将互换分为三组:建设性互换、破坏性互换和无害互换。
建设性交换被定义为一种交换,它导致至少一个元素移动到正确的位置。
破坏性交换被定义为导致至少一个元素从其正确位置移动的交换。
无害交换被定义为不属于其他组的交换。
很容易看出这是所有可能交换的分区。 (交集 = 空集)。
现在我要证明的主张:
The array will remain the same if and only if
the number of Destructive swap == Constructive swap in the iterations.
如果有人有反例,请写下来作为评论。
如果这个说法是正确的,我们可以把所有的组合加起来—— 0 次无害交换,1 次无害交换,..,N 次无害交换。
对于每个可能的 k 无害交换,我们检查 N-k 是否是偶数,如果不是,我们跳过。如果是,我们将 (N-k)/2 用于破坏性,将 (N-k) 用于建设性。看看所有的可能性。
【讨论】:
你的推理是错误的,因为我们交换了 N 次,即概率是 1 / N^2。但这也是错误的,因为有多种因素,其中一些至少已经在其他答案中解释过。 @KonradRudolph 你在哪里看到我写的概率是 1/N^2?我确信我写的是正确的。 您能解释一下原因吗? (哪里错了)我很确定不是。 我的第一条评论难道没有说明问题所在吗?对于 一个 交换,n = m 的概率是 1/N。您这样做 N 次,每次都需要相同,即 1/N * 1/N = 1/N^2。基本上,您的答案中 P2 的计算是错误的。直觉上应该很明显,保留原始数组的机会应该比 N 中的 1 小很多,很多。 等等,我上面评论中的推理是错误的,你不能乘以概率,因为它们是有条件的,但你的 P2 公式仍然是错误的,直觉仍然成立。【参考方案11】:我会将问题建模为一个多重图,其中节点是数组的元素,而交换是在它们之间添加一个无向(!)连接。然后以某种方式寻找循环(所有节点都是循环的一部分 => 原始)
真的需要回去工作了! :(
【讨论】:
【参考方案12】:每个人都认为A[i] == i
,这不是明确的
表示。我也会做这个假设,但请注意概率
取决于内容。例如,如果A[i]=0
,那么概率 = 1
都是N。
这里是如何做到这一点。设P(n,i)
为结果数组的概率
与原始数组正好有 i 个转置。
我们想知道P(n,0)
。确实是这样:
P(n,0) =
1/n * P(n-1,0) + 1/n^2 * P(n-1,1) =
1/n * P(n-1,0) + 1/n^2 * (1-1/(n-1)) * P(n-2,0)
解释:我们可以通过两种方式获得原始数组,或者通过在已经好的数组中进行“中性”转置,或者通过恢复唯一的“坏”转置。为了得到一个只有一个“坏”转置的数组,我们可以取一个有 0 个坏转置的数组,并制作一个非中性的转置。
编辑:P(n-1,0) 中的 -2 而不是 -1
【讨论】:
1) 问题指出“假设数组包含唯一元素”。所以不失一般性,你可以假设A[i] == i
。 2) 我认为一般来说没有一种简单的方法可以找到P(n,i)
。 P(n,0)
很容易,但对于较大的i
,不清楚有多少转置是“好”的,有多少是“坏”。
@sdcvvc:你说很容易找到P(n,0)
,但这正是问题所在。如果您不同意这种推理,请举一个失败的例子,或者为什么它不正确。
我不认为P(n-1,1) = (1-1/(n-1)) * P(n-2,0)
。如果您在中间,您可以添加另一个稍后将撤消的错误步骤。例如,考虑 (1234) => (2134) => (2143) => (1243) => (1234)。分母中的数字 n-1 也是可疑的,因为随机函数允许重复。【参考方案13】:
嗯,从数学的角度来看:
要让数组元素每次都在同一个位置交换,那么 Rand(N) 函数必须为 int m 和 int n 生成两次相同的数字。所以 Rand(N) 函数两次生成相同数字的概率是 1/N。 我们在 for 循环中调用了 N 次 Rand(N),所以我们的概率是 1/(N^2)
【讨论】:
【参考方案14】:算法的行为可以建模为Markov chain 上的symmetric group SN。
基础知识
数组A
的N个元素可以排列成N!不同的排列。让我们将这些排列从 1 编号到 N!,例如按字典顺序。所以算法中任意时刻数组A
的状态都可以完全用置换数来表征。
例如,对于 N = 3,所有 3 中的一种可能编号! = 6 个排列可能是:
-
a b c
a c b
b a c
b c a
c a b
c b a
状态转移概率
在算法的每一步中,A
的状态要么保持不变,要么从这些排列中的一个转换到另一个。我们现在对这些状态变化的概率感兴趣。让我们称Pr(i → j)为状态从排列i变为排列j 在单个循环迭代中。
当我们从 [0, N-1] 范围内统一且独立地选择 m 和 n 时,有 N ² 可能的结果,每个结果的可能性都一样。
身份
对于这些结果中的 N 个 m = n 成立,因此排列没有变化。因此,
.
换位
剩余的 N² - N 种情况是转置,即两个元素交换它们的位置,因此排列发生变化。假设这些换位之一交换位置 x 和 y 的元素。算法如何生成这种转置有两种情况:m = x, n = y 或 m = y, n = x。因此,每个转置的概率为 2 / N²。
这与我们的排列有什么关系?当且仅当存在将 i 转换为 j(反之亦然)。然后我们可以得出结论:
转移矩阵
我们可以将概率 Pr(i → j) 安排在一个转移矩阵 P ∈ [0,1]N!×N!。我们定义
pij = Pr(i → j),
其中 pij 是 i-th 行和 j-th 列中的条目 P。请注意
Pr(i → j) = Pr(j → i),
表示P是对称的。
现在的关键是观察当我们将 P 自身相乘时会发生什么。取P²的任意元素p(2)ij:
乘积 Pr(i → k) · Pr(k → j) 是概率从排列 i 开始,我们在一个步骤中转换为排列 k,并在另一个后续步骤之后转换为排列 j。因此,对所有中间排列 k 求和,可以得出 在 2 个步骤中从 i 转换到 j 的总概率.
这个论点可以扩展到P的更高次幂。一个特殊的后果如下:
p(N)ii 是概率在 N 步之后返回排列 i,假设我们从排列 i 开始。
示例
让我们用 N = 3 来解决这个问题。我们已经有了排列的编号。对应的转移矩阵如下:
将 P 与自身相乘得到:
另一个乘法产生:
主对角线的任何元素都会给我们想要的概率,即 15/81 或 5/27子>.
讨论
虽然这种方法在数学上是合理的,并且可以应用于 N 的任何值,但在这种形式下不太实用。转换矩阵 P 有 N!² 个条目,它变得非常快。即使 N = 10,矩阵的大小也已经超过 13 万亿个条目。因此,这种算法的简单实现似乎是不可行的。
但是,与其他提议相比,这种方法非常结构化,除了确定哪些排列是邻居之外,不需要复杂的推导。我希望可以利用这种结构化来找到更简单的计算。
例如,可以利用 P 的任何幂的所有对角元素都相等这一事实。假设我们可以很容易地计算出 PN 的轨迹,那么解决方案就是 tr(PN) / N!. PN的迹等于其特征值的N次方之和。因此,如果我们有一个有效的算法来计算 P 的特征值,我们就会被设置。然而,除了计算 N = 5 的特征值之外,我还没有进一步探索这个问题。
【讨论】:
浏览一下,这似乎是整个主题的唯一正确/正确答案。你有没有运气概括结果?我今晚去看看。 @Gleno 除了我已经写的内容之外,我还没有研究过它。我很想听听你的发现! 好吧,我还没有时间解决这个问题。我验证了您的发现(对于 n = 3 导出相同的矩阵、相同的概率等)并计算了最高为 N = 7 的特征值和概率。显然有一个模式,但在查看特征值序列时它并没有跳出来.我还尝试通过查看矩阵的对角元素的 n 次方来作弊,并检查它们是否遵循任何已知的序列 - 但可惜他们没有。有点遗憾的是,您的方法在这一点上产生了高达 N=7 的概率,但在页面上却是如此之低。【参考方案15】:C# 中的简单实现。 这个想法是创建初始数组的所有可能排列并枚举它们。 然后我们建立一个可能的状态变化矩阵。将矩阵与自身相乘 N 次,我们将得到矩阵,该矩阵显示在 N 步中从排列 #i 到排列 #j 存在多少种方式。 Elemet [0,0] 将显示有多少种方式会导致相同的初始状态。第 0 行的元素总和将显示不同方式的总数。通过将前者除以后者,我们得到概率。
实际上排列的总数是 N^(2N)。
Output:
For N=1 probability is 1 (1 / 1)
For N=2 probability is 0.5 (8 / 16)
For N=3 probability is 0.1851851851851851851851851852 (135 / 729)
For N=4 probability is 0.068359375 (4480 / 65536)
For N=5 probability is 0.0193664 (189125 / 9765625)
For N=6 probability is 0.0057198259072973293366526105 (12450816 / 2176782336)
class Program
static void Main(string[] args)
for (int i = 1; i < 7; i++)
MainClass mc = new MainClass(i);
mc.Run();
class MainClass
int N;
int M;
List<int> comb;
List<int> lastItemIdx;
public List<List<int>> combinations;
int[,] matrix;
public MainClass(int n)
N = n;
comb = new List<int>();
lastItemIdx = new List<int>();
for (int i = 0; i < n; i++)
comb.Add(-1);
lastItemIdx.Add(-1);
combinations = new List<List<int>>();
public void Run()
GenerateAllCombinations();
GenerateMatrix();
int[,] m2 = matrix;
for (int i = 0; i < N - 1; i++)
m2 = Multiply(m2, matrix);
decimal same = m2[0, 0];
decimal total = 0;
for (int i = 0; i < M; i++)
total += m2[0, i];
Console.WriteLine("For N=0 probability is 1 (2 / 3)", N, same / total, same, total);
private int[,] Multiply(int[,] m2, int[,] m1)
int[,] ret = new int[M, M];
for (int ii = 0; ii < M; ii++)
for (int jj = 0; jj < M; jj++)
int sum = 0;
for (int k = 0; k < M; k++)
sum += m2[ii, k] * m1[k, jj];
ret[ii, jj] = sum;
return ret;
private void GenerateMatrix()
M = combinations.Count;
matrix = new int[M, M];
for (int i = 0; i < M; i++)
matrix[i, i] = N;
for (int j = i + 1; j < M; j++)
if (2 == Difference(i, j))
matrix[i, j] = 2;
matrix[j, i] = 2;
else
matrix[i, j] = 0;
private int Difference(int x, int y)
int ret = 0;
for (int i = 0; i < N; i++)
if (combinations[x][i] != combinations[y][i])
ret++;
if (ret > 2)
return int.MaxValue;
return ret;
private void GenerateAllCombinations()
int placeAt = 0;
bool doRun = true;
while (doRun)
doRun = false;
bool created = false;
for (int i = placeAt; i < N; i++)
for (int j = lastItemIdx[i] + 1; j < N; j++)
lastItemIdx[i] = j; // remember the test
if (comb.Contains(j))
continue; // tail items should be nulled && their lastItemIdx set to -1
// success
placeAt = i;
comb[i] = j;
created = true;
break;
if (comb[i] == -1)
created = false;
break;
if (created)
combinations.Add(new List<int>(comb));
// rollback
bool canGenerate = false;
for (int k = placeAt + 1; k < N; k++)
lastItemIdx[k] = -1;
for (int k = placeAt; k >= 0; k--)
placeAt = k;
comb[k] = -1;
if (lastItemIdx[k] == N - 1)
lastItemIdx[k] = -1;
continue;
canGenerate = true;
break;
doRun = canGenerate;
【讨论】:
以上是关于数组保持不变的概率是多少?的主要内容,如果未能解决你的问题,请参考以下文章