改组算法分析

时间:2011-09-03 06:02:02

标签: algorithm

我发现了以下对改组算法的分析:

  

问:给定一组不同的整数,给出一个随机的算法   重新排序整数,以便每个可能的重新排序是相同的   有可能。换句话说,如果有一副纸牌,你怎么能洗牌   他们这样任何卡片的排列同样可能吗?

     

答案很好:按顺序浏览元素,用a替换每个元素   数组中的随机元素不会出现在   元件。这需要O(n)时间。请注意,有几种可能   这个问题的解决方案,以及几个好看的答案   这是不正确的。例如,对上述内容略有修改   算法,其中一个元素切换每个元素与任何元素   数组不会以相同的概率给每次重新排序

我想知道的是,与使用Knuth shuffle(描述)相比,为什么用数组中的任何其他元素切换每个元素不会产生良好的混乱。另外,Knuth如何以相同的概率选择值?非常感谢任何数学或证明。

4 个答案:

答案 0 :(得分:20)

该算法不能产生均匀随机排列的最简单证明

for (int i = 0; i < 3; ++i) {
   swap(a[i], a[rand() % 3]);
}

它是否会产生27种可能的结果,但只有3种! = 6个排列。由于6不分27,所以必须有一些排列被挑选得太多,而有些则被挑选得很少。

为什么O(n)算法是最优的?好吧,随机shuffle有时必须触摸每个输入(改变它们),所以任何最优算法都需要至少做O(n)工作。

为什么Knuth算法正确?这需要更多的洞察力。您可以通过归纳证明第一项是以正确的概率选择的(每个项目同样可能是第一项),然后证明当您在循环中前进时归纳步骤成立,第二,第三等项目是也从阵列的其余部分以正确的概率选择。

答案 1 :(得分:6)

考虑一个三元素列表。它具有这些可能的状态和相关概率:

1 [a, b, c] (0)

在第一次改组操作中,a有1/3的机会与任何元素交换,因此可能的状态和相关概率如下:

From (0)
1/3 [a, b, c] (1)
1/3 [b, a, c] (2)
1/3 [c, b, a] (3)

在第二次改组操作中,除了第二个插槽外,同样的事情再次发生,所以:

From (1) ([a, b, c])
1/9 [b, a, c] (4)
1/9 [a, b, c] (5)
1/9 [a, c, b] (6)
From (2) ([b, a, c])
1/9 [a, b, c] (7)
1/9 [b, a, c] (8) 
1/9 [b, c, a] (9)
From (3) ([c, b, a])
1/9 [b, c, a] (10)
1/9 [c, b, a] (11)
1/9 [c, a, b] (12)

在第三次改组操作中,除了第三个插槽外,同样的事情发生了,所以:

From (4) ([b, a, c])
1/27 [c, a, b] (13)
1/27 [b, c, a] (14)
1/27 [b, a, c] (15)
From (5) ([a, b, c])
1/27 [c, b, a] (16)
1/27 [a, c, b] (17)
1/27 [a, b, c] (18)
From (6) ([a, c, b])
1/27 [b, c, a] (19)
1/27 [a, b, c] (20)
1/27 [a, c, b] (21)
From (7) ([a, b, c])    
1/27 [c, b, a] (22)
1/27 [a, c, b] (23)
1/27 [a, b, c] (24)
From (8) ([b, a, c])
1/27 [c, a, b] (25)
1/27 [b, c, a] (26)
1/27 [b, a, c] (27)
From (9) ([b, c, a])
1/27 [a, c, b] (28)
1/27 [b, a, c] (29)
1/27 [b, c, a] (30)
From (10) ([b, c, a])
1/27 [a, c, b] (31)
1/27 [b, a, c] (32)
1/27 [b, c, a] (33)
From (11) ([c, b, a])
1/27 [a, b, c] (34)
1/27 [c, a, b] (35)
1/27 [c, b, a] (36)
From (12) ([c, a, b])
1/27 [b, a, c] (37)
1/27 [c, b, a] (38)
1/27 [c, a, b] (39)

结合相似的条款,我们得到:

4/27 [a, b, c] From (18), (20), (24), (34)
5/27 [a, c, b] From (17), (21), (23), (28), (31)
5/27 [b, a, c] From (15), (27), (29), (32), (37)
5/27 [b, c, a] From (14), (19), (26), (30), (33)
4/27 [c, a, b] From (13), (25), (35), (39)
4/27 [c, b, a] From (16), (22), (36), (38)

这显然是不平衡的。

只从尚未选择的元素中选择的shuffle是正确的。为证明我提出这个:

考虑一下你有一袋元素。如果您从该包中随机选择并将结果元素放在列表中,您将获得一个随机排序的列表。这基本上只与那些尚未被选中的元素进行交换(考虑将您放置为列表开头的列表,以及可以与之交换的列表尾部)。

答案 2 :(得分:3)

首先,所描述的算法是O(n)并不是相当,尽管它非常接近。它应该是O(n * log(n))。

这就是原因:第一次交换需要从n个元素中抽取,然后是n-1 ...... 2.但是从n个元素中选择的复杂性应该是log(n),因为你必须生成log(n)随机位。

rrenaud给出了一个很好的论据,即“坏”算法并不统一,所以我会试着说“好”算法是统一的。每一步你从n,n-1,... 1中选择一个,所以最终总共有n个!你可以做出的选择。既然有n!如果每个安排都可以通过至少一个选择序列到达,则可以通过恰好一个选择序列来达到排列列表的方式。因此,为了表明它是统一的,我们只需要表明给定一些可能的排序,我们可以通过一系列选择来达到它。

现在问题看起来很简单。假设你从

开始
  

a b c d e

你想得到

  

b c d e a

将光标放在第0个元素上。你应该换哪个? b,因为你想将它移动到0位置。现在进步。在每个步骤中,所有元素“在”后面都在正确的位置,所以当你到达最后,所有元素都在正确的位置。

答案 3 :(得分:1)

首先,请注意Knuth的方式必须是均匀随机的,因为这基本上等同于从堆栈A中绘制随机卡并通过以随机顺序将它们放下来形成堆栈B.这必须是随机的。

要看到另一种方式是坏的,只要表明不同结果的数量排除了统一的结果就足够了。有52 ^ 52种方法可以在1到52之间选择52个随机整数。但是,有52个!这些整数的排列。 52!有47个因子,而52 ^ 52没有; 52岁!不均匀划分52 ^ 52。这意味着至少有一个排列导致它的结果比其他排列更多......为了看到这一点,尝试平均分割结果直到你用完为止。由于结果的数量不是排列数的倍数,因此不能给每个人相同的数量。换句话说,如果你放弃所有吸盘,你就不能将12个吸盘平均分配给5个孩子。同样的原则。