从原数组生成随机打乱数组(不使用内置shuffle)的实现问题
不依赖内置shuffle函数的数组随机打乱实现
现有代码的问题
你当前的实现有两个核心bug,才会出现元素重复选取、结果和原数组元素不匹配的问题:
- 循环写法错误:
for i in Deck1取到的i是Deck1里的元素值,不是数组索引。如果Deck1存在重复值、或者元素值不在0~n-1的连续范围内,会直接导致Deck2的赋值位置错乱。 - 随机选取无去重逻辑:每次生成随机位置j之后没有做任何已选标记,同一个位置的元素可能被多次取出,同时也会有原数组位置永远没被选中的情况,最终Deck2的元素组成和原数组不一致,也没法保证重复值的数量匹配。
正确实现思路
不要尝试基于元素值做选取校验:如果原数组存在重复值,你根本无法通过值判断当前拿到的是未被选取的重复元素,还是同一个元素被重复选中了。
正确的校验维度是原数组的索引——每个位置的索引是全局唯一的,只要保证每个索引刚好被选取一次,不管对应位置的元素值是否重复,最终生成的新数组都会和原数组的元素组成完全一致,重复值的数量也会完全对应。
两种常用的无内置shuffle实现方案:
- 索引池方案:初始化时把所有0~n-1的索引存入待选列表,每次随机从列表里挑一个索引,取出对应原数组元素放到新数组后,把这个索引从待选列表里移除,直到列表为空。
- Fisher-Yates洗牌算法:空间复杂度更低的原地打乱方案,从数组末尾向前遍历,每次在当前遍历位置之前的范围里随机选一个位置,交换两个位置的元素,遍历完成即得到均匀打乱的结果,不需要额外的标记数组。
修正后的可运行代码
import numpy as np def shuffler(): n = int(input("请输入牌堆的卡牌数量: ")) s = input("是否需要打乱牌堆? (Y/N): ").lower() # 初始化原牌堆 if s == "y": Deck1 = np.random.randint(low=0, high=n, size=n) else: Deck1 = np.arange(n) print("原牌堆:") print(Deck1) Deck2 = np.empty(n, dtype=np.int_) # 初始化待选索引池 remain_index = list(range(n)) for pos in range(n): # 从剩余未选索引中随机取一个 pick_pos = np.random.randint(0, len(remain_index)) selected_idx = remain_index.pop(pick_pos) Deck2[pos] = Deck1[selected_idx] # 如果不需要保留原Deck1,可以替换为Fisher-Yates原地洗牌实现,空间开销更低 # Deck2 = Deck1.copy() # for i in range(n-1, 0, -1): # j = np.random.randint(0, i+1) # Deck2[i], Deck2[j] = Deck2[j], Deck2[i] print("打乱后的牌堆:") print(Deck2)
两种实现都天然兼容原数组存在重复值的场景,不需要针对重复值写额外的判断逻辑。
内容的提问来源于stack exchange,提问作者Ivan
相关产品推荐
相关产品推荐

