【问题标题】:How to generate two random numbers, X and Y, and make sure they are never repeated?如何生成两个随机数 X 和 Y,并确保它们永远不会重复?
【发布时间】:2014-08-08 20:43:42
【问题描述】:

好的,所以我正在 WinForms 中编写扫雷应用程序。这是我的第一个 C# 应用程序,因此这是一个巨大的学习过程。我想随机生成两个数字以放入两个单独的数组 mineX 和 mineY 中。这两个数组是所有地雷将在棋盘上放置的坐标。

这就是问题所在,我想确保当它随机生成这些数字时,X、Y 永远不会相同。我已经完成了研究,并且知道如何生成一个数字并使每个数字都独一无二。我的问题是我希望两个随机数的组合是唯一的。

这是我的 placeMines 方法的代码。

    private void placeMines()
    {
        Random rnd = new Random();

        for (int i = 0; i < MINE_COUNT; i++)
        {               
                bombX[i] = rnd.Next(0, BOARD_X);
                bombY[i] = rnd.Next(0, BOARD_Y)'
        }
    }

这似乎是一个很容易解决的问题,但我一辈子都想不通。

【问题讨论】:

  • 你的意思是 X,Y 的组合必须是唯一的,还是说只要选择了 X=2,其他 X 就不能是 2?
  • 对,两者的结合。 X 实际上可能多次等于 2,但对应的 Y 必须不同。
  • 如果它们不能重复,那么你就不需要随机数。所以不要使用随机数生成器!
  • 当您需要解决此类问题时,识别模式很重要。您不想要一个随机值的集合,您想要一个随机的 order 固定值。称为洗牌,因其与洗牌非常相似而得名。随机播放的标准算法称为 Fisher Yates,以 1938 年解决它的两位统计学家的名字命名。当您查询“c# random shuffle”或“c#fisher yates”时,您将获得大量的 Google 搜索结果。仅此网站就有 11000 个,我们尽量不添加更多 :)

标签: c# random


【解决方案1】:
  1. 创建可能的地雷位置列表。
  2. 从列表中随机选择一项,将其移除。
  3. 重复 2 直到有足够的地雷。

示例代码:

const int BOARD_X = 10;
const int BOARD_Y = 6;
const int MINE_COUNT = 30;

List<int> positions = Enumerable.Range(0, BOARD_X * BOARD_Y).ToList();
var rnd = new Random();
for (int i = 0; i < MINE_COUNT; i++) {
    int index = rnd.Next(positions.Count);
    int pos = positions[index];
    positions.RemoveAt(index);
    int x = pos % BOARD_X, y = pos / BOARD_X;
    Console.WriteLine("({0}, {1})", x, y);
}

注意:如果位置的数量很大,您可以通过使用位向量来优化空间算法。我将把它留作练习。

【讨论】:

  • 谢谢!这很有意义。我还没有了解列表,所以我的印象是我无法在不转换为字符串 (intX+","+intY) 的情况下将一对整数作为一个整体进行比较,然后再比较字符串。欣赏它。
  • @user3922214 如果您不想像这样在一个整数中编码两个整数(例如为了可读性),您可以使用元组(参见另一个答案)、自定义类型或找到合适的来自 .NET 框架的类型(WinForms、WPF、XNA 中有点类)。
【解决方案2】:

在数组中生成所有位置(让它成为具有两个 int 字段 x、y 的结构)并打乱这个数组。

Random _rand = new Random();
Position[] allPositions = GenerateAllPositions();

for (int i=allPositions.Length-1; i>=1; i--)
{
     int j = _rand.Next(0, i+1);
     // now swap
     Position tempPosition = allPositions[i];
     allPositions[i] = allPositions[j];
     allPositions[j] = tempPosition;
}

现在您可以拥有临时 int 变量 lastQueriedPositionIndex = 0,当您选择任何位置时将其加一。如果lastQueriedPositionIndex == allPositions.Length 生成lastQueriedPositionIndex=0 并再次调用此“交换”例程。这是 O(n) 最坏情况解决方案,而不是 Athari 提出的 O(n^2) 解决方案 - 但是在小板尺寸中您不会看到任何差异。

【讨论】:

  • 您为每个位置调用随机数生成。我怀疑这可能会很慢,因为 O 符号隐藏了乘法器。我认为没有明显的赢家。
  • @Athari 随机数生成速度非常快,像这样(这是一个fisher yates shuffle)对列表进行洗牌根本不会占用太多资源。实际上,您的解决方案是缓慢的,因为每次删除元素时都需要复制每个项目。因此,这将是 O(2N) 次读取,而您删除项目的解决方案将是 O(N^2) 次读取。
  • @ScottChamberlain 测量。当地雷比例真实时,这种方法大约快 5-10 倍(在普通难度的经典 MS 扫雷中为 15%)。我的方法在比例很低的情况下可以更快,但是很难玩。
  • @ScottChamberlain 一个小问题 - O(2N) 是 O(N)。
猜你喜欢
  • 2015-04-07
  • 1970-01-01
  • 1970-01-01
  • 2013-11-09
  • 2012-01-07
  • 1970-01-01
  • 2011-03-06
  • 1970-01-01
  • 2020-07-16
相关资源
最近更新 更多