【问题标题】:Replacing a number in a array if duplicates are found with a random numbe如果使用随机数找到重复项,则替换数组中的数字
【发布时间】:2017-08-22 12:54:43
【问题描述】:

我正在尝试生成一个指定大小的数组,用随机整数填充它,然后遍历该数组以检查是否没有重复项。我在测试时遇到了一些不一致的地方。想知道是否可以使用嵌套的 for 循环来完成,或者是否建议了更好的方法?

class Program
{
    static void Main(string[] args)
    {
        int size = 10;
        int[] newArray = InitializeArrayWithNoDuplicates(size);
          for (int i = 0; i < newArray.Length; i++)
        {
          Console.WriteLine( newArray[i]);
        }

        Console.ReadKey(); 
    }

    static Random rng = new Random();

    public static int[] InitializeArrayWithNoDuplicates(int size)
    {
        int minValue = 1;
        int maxValue = 10; 
        int[] array = new int[size] ;

        for (int i = 0; i <array.Length; i++)
        {
           array[i] = rng.Next(minValue, maxValue);

              for (int j = i+1; j < array.Length-1; j++)
                    if (array[i] == array[j])
                     {
                    array[i] = rng.Next(minValue, maxValue);
                     }
           }return array;
    } 
}

【问题讨论】:

  • 很难找到 10 个在 1..10 范围内没有重复的随机整数;) 如果您在不增加允许范围的情况下增加数组的大小,程序将无法完成任务!
  • 您将数组从0 填充到length-1,对吗?但是,一旦你设计了一个新的i-th 项目,你将它与索引i+1array.length-1 处的数组的未初始化部分进行比较,而不是与那些已经在0i-1 填充的部分进行比较。 ..!您希望它如何确保价值的唯一性?
  • @CiaPan - 这不是一个难题。一点儿都没有。每个模拟一副纸牌的程序都解决了这个确切的问题,并且“问题规模”超过了 5 倍。 OP的做法是错误的,但这和问题难不一样。
  • @MarkAdelsberger 如果我看到一个程序通过随机猜测卡片并丢弃重复卡片来“发牌”,我会停止与它的作者合作并自己编写程序。它在 IMO 的 bogosort 旁边。选择 K 张牌的更快、一次性且无重复检查的方法是洗牌并取前 K 个物品。或者生成 K 个随机索引(允许重复)以减少牌组,并在每次迭代中在生成的索引处删除一张牌。
  • @CiaPan - 您不理解“OP 的方法错误”的哪一部分?您只是描述了一种不同的算法,用于“查找 [ing] [n] 随机 ... 在 1..[n] 范围内没有重复的数字”。想嚣张,先尝试正确。

标签: arrays duplicates


【解决方案1】:

您的重复消除逻辑不起作用。考虑数组以[1, 2, ...] 开头的情况,您正在设置第三个元素。你随机生成一个2

然后你开始你的内循环。当j == 1 比较显示没有问题。当j == 2 你看到你的新值将是一个重复,所以你重新滚动。但是您的重投可能是 1(第一个元素的副本,您不再检查)或 2(第二个元素的副本,您不再检查)。 p>

更一般地说,这种方法效率很低。对于这种特殊情况(将所有 10 个数字从 1 到 10 随机排列),这样的事情可能会更好:

  • 创建可用值的列表 L(每个数字从 1 到 10)
  • 当 L 不为空时
    • 生成一个介于 1 和 L 大小之间的随机数 N
    • 将输出数组的下一个开放元素设置为 L 的第 N 个元素
    • 从 L 中删除第 N 个元素

有很多方法可以实现这种方法。不要害怕维护合适的数据结构的开销;与具有嵌套循环和随机/可能无限重试的算法的算法成本相比,这算不上什么,这是您最初方法的发展方向。

【讨论】:

  • 虽然您的建议是正确的,但基础分析存在缺陷。请注意,对于array == [1, 2, ...]i==2,提供的代码不会将新的array[i]array[0] 进行比较,因为内部循环从j=i+1 开始,即索引3,并继续前进。
  • @CiaPan :当算法是问题时,我不会因为代码拼写错误而挂断电话。代码的意图很明确,本次分析解决了这一意图。
  • 谢谢,我现在明白这个逻辑了。对于这个问题,我得到了一个介于 1 到 45 之间的初始范围,但我如何限制更快地解决算法问题,现在我意识到这部分逻辑中的错误。感谢您注意到索引不正确,我已对此进行了修改,但仍有问题。我将尝试使用您的方法 Mark Adelsberger。谢谢
【解决方案2】:

Next(minValue, maxValue)中的第一个参数是包含下界,第二个参数是独占界,即返回值应大于等于minValue且小于maxValue

https://msdn.microsoft.com/pl-pl/library/2dx6wyd4(v=vs.110).aspx

因此,对于

    int minValue = 1;
    int maxValue = 10; 

生成器只能返回九个不同的值,19,并且您无法用它们填充 10 项目 array[] 并使它们唯一。

您必须假设数组更短,或者允许随机值的跨度更大。
或者允许重复...

对于相当短的数据集,最快的方法是填充一个数组并生成它的排列。假设您要将数字 1..N 放入 N 项数组中。先准备好数组:

    int[] array = new int[N];

    for (int i = 0; i < N; ++i)
        array[i] = i+1;

然后置换它。这个洗牌算法从i==N-1向下迭代到1,并在每一步中选择一个项目放在索引i

    for (int i = N-1; i > 0; --i)
    {
        int j = rng.Next (0, i+1);
        swap (array[j], array[i]);    // exchange values
    }

请注意,在每次迭代中,我们从0i 取随机索引j ,因此j==i 可能表示array[i] 的值留在原地。这样,在每次迭代中,我们从尚未绘制的那些中提取一个新数字并将其放在连续的位置,结果我们以相等的概率获得array 的每个可能排列(假设随机数生成器Next 方法总是返回值在其参数定义的区间内均匀分布)。
i 到达0 时循环终止——索引0 下没有项目我们可以选择与array[0] 交换。

如果您的数据跨度比所需的数组长度稍长,但两个值都非常小 - 例如,您需要 100 个值中的 80 个 - 您可以创建一个数组来保留整个集合(即 100 items),填充它,随机播放,然后丢弃不需要的 20。
如果您选择从数组的开头删除项目,您也可以放弃最后 20 步的洗牌。

对于非常大的可能值集,这将是无效的。假设您需要一亿个值中的 500 个值的随机序列——您是否想分配、填充和打乱 100,000,000 个项目数组以获得仅 500 个值?当然不是!
为简单起见,假设您的数据空间又是一个整数区间,您可以构建一个整数的自平衡 BST。最初,树是空的。
然后你将这一步迭代 500 次:生成一个随机数 K 并在树中寻找它;如果它不存在,则添加它,它会成为您的输出值之一。如果您找到数字 K,它是重复的,您重新迭代该步骤,从 rng 绘制另一个 K。
重选同一个数的概率很低,上一次迭代是500到1亿,也就是1/200,000。无论如何,它可能发生。

对于像自平衡 BST 这样的结构,寻找值可以限制为 N 的对数,因此对于 N 个输出值,您执行 N 次迭代,并且每次执行 log(N) 步来检测一个可能重复,因此整个时间复杂度约为 N·log(N)。

其他结构可能会提供更快的重复检测,从而缩短执行时间。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-02
    • 1970-01-01
    • 1970-01-01
    • 2020-08-14
    • 2012-06-24
    • 2019-04-19
    • 1970-01-01
    相关资源
    最近更新 更多