Python 为什么我的洗牌实现不正确?
当我想洗牌一个序列时,我会使用Python 为什么我的洗牌实现不正确?,python,algorithm,shuffle,Python,Algorithm,Shuffle,当我想洗牌一个序列时,我会使用random.shuffle。我已经阅读了random.shuffle的源代码,它是的一个典型实现 然而,我曾经看到一个不正确的洗牌算法实现。代码如下: def myshuffle(lst): length = len(lst) for idx in xrange(length): t_idx = random.randint(0, length-1) lst[idx], lst[t_idx] = lst[t_idx]
random.shuffle
。我已经阅读了random.shuffle的源代码,它是的一个典型实现
然而,我曾经看到一个不正确的洗牌算法实现。代码如下:
def myshuffle(lst):
length = len(lst)
for idx in xrange(length):
t_idx = random.randint(0, length-1)
lst[idx], lst[t_idx] = lst[t_idx], lst[idx]
我知道有问题,我已经测试过了。但我不清楚为什么这是不正确的。假设p[i][j]
表示元素从posi
移动到posj
的概率,有人能说清楚吗
这是我的测试代码
if __name__ == '__main__':
random.seed()
pre_lst = ['a', 'b', 'c', 'd', 'e']
count = dict((e, {}) for e in pre_lst)
TRY = 1000000
for i in xrange(TRY):
lst = pre_lst[:]
myshuffle(lst)
for alpha in pre_lst:
idx = lst.index(alpha)
count[alpha][idx] = count[alpha].get(idx, 0) + 1
for alpha, alpha_count in sorted(count.iteritems(), key=lambda e: e[0]):
result_lst = []
for k, v in sorted(alpha_count.iteritems(), key=lambda e: e[0]):
result_lst.append(round(v * 1.0 / TRY, 3))
print alpha, result_lst
结果是:
> a [0.2, 0.2, 0.2, 0.2, 0.2]
> b [0.242, 0.18, 0.185, 0.192, 0.2]
> c [0.21, 0.23, 0.173, 0.186, 0.2]
> d [0.184, 0.205, 0.231, 0.18, 0.2]
> e [0.164, 0.184, 0.21, 0.242, 0.2]
数学上:
该算法不可能产生同样可能的结果:该算法有n^n
不同的循环方式(n
迭代随机选取n
索引中的一个),每一种同样可能的循环方式产生n代码>可能的排列。但是n^n
几乎永远不能被n整除代码>。因此,该算法不能产生均匀分布
与每次n
迭代时,交换索引池减少1
。这里,正好有n代码>通过树的路径,每个路径恰好产生一个n代码>可能的排列
对于短列表(n您能举一个失败的例子吗?您是如何播种的?我现在还不清楚该算法有什么问题,但您可能应该反向思考这个问题,也就是说,您能证明该算法以相同的概率生成任何排列吗?或者(我认为是等效的)每个元素在任何位置结束的概率都是相同的?如果不是,那只是一种启发式算法(一种“直觉上”似乎合理但尚未被证明是正确的算法)。“算法的错误”在于它没有正确地实现Fisher-Yates。随机索引t_idx
不应该介于(0,length-1)
应该是(idx,length-1)
。在Fisher-Yates中,您不需要重新整理您已经讨论过的部分(Schwobasegl的回答解释了您当前实现中遇到的问题以及它的重要性),非常感谢。您能推导出p[i][j]的公式吗?我试过了,但没有结果。@xybay,你可以从经验案例中得出它,把我移到j的所有计数相加,除以总计数
def shuffle_combos(lst, i=0):
l = len(lst)
for j in range(l):
lst_ = lst[:]
lst_[i], lst_[j] = lst_[j], lst_[i]
if i == l-1:
yield tuple(lst_)
else:
for perm in shuffle_combos(lst_, i=i+1):
yield perm
>>> from pprint import pprint
>>> from collections import Counter
>>> pprint(list(Counter(shuffle_combos([1,2,3])).items()))
[((1, 3, 2), 5),
((3, 2, 1), 4),
((2, 3, 1), 5),
((1, 2, 3), 4),
((2, 1, 3), 5),
((3, 1, 2), 4)]
# ^- 3^3 = 27 paths, but 3! = 6 permutations
# but 27 % 6 != 0
>>> pprint(list(Counter(shuffle_combos([1,2,3,4])).items()))
[((4, 1, 2, 3), 8),
((1, 3, 2, 4), 10),
((3, 4, 1, 2), 11),
((1, 2, 4, 3), 10),
((1, 2, 3, 4), 10),
((1, 3, 4, 2), 14),
((1, 4, 2, 3), 11),
((4, 2, 1, 3), 9),
((2, 4, 3, 1), 11),
((2, 1, 3, 4), 10),
((4, 2, 3, 1), 8),
((3, 1, 2, 4), 11),
((4, 3, 1, 2), 10),
((2, 4, 1, 3), 11),
((2, 3, 1, 4), 14),
((3, 1, 4, 2), 11),
((3, 4, 2, 1), 10),
((1, 4, 3, 2), 9),
((3, 2, 4, 1), 11),
((2, 3, 4, 1), 14),
((4, 1, 3, 2), 9),
((4, 3, 2, 1), 10),
((3, 2, 1, 4), 9),
((2, 1, 4, 3), 15)]