【问题标题】:Array Contains Performance数组包含性能
【发布时间】:2017-01-07 05:30:03
【问题描述】:

我有一个使用数组的程序,有时我必须检查一个 lsit 值是否在数组中,执行此操作的函数需要大约 70% 的程序 CPU 时间,所以我想知道是否有一种更有效地做到这一点的方法。

这些是我的功能:

private static int[] GenerateRow(int length, Random RNG)
    {
        int[] row = InitalizeRow(length);
        int index = 0;
        while (!AreAllNumbersGenerated(row))
        {
            int value = RNG.Next(0, length);
            if (!RowContains(row, value))
            {
                row[index] = value;
                index++;
            }
        }
        return row;
    }  

    private static bool AreAllNumbersGenerated(int[] row)
    {           
        for (int i = 0; i < row.Length; i++)            
           if(!RowContains(row, i))
                return false;           
        return true;
    }

    private static bool RowContains(int[] row, int value)
    {
        for (int i = 0; i < row.Length; i++)
            if (row[i] == value)
                return true;
        return false;
    }

所有的工作都是由AreAllNumbersGenerated(),然后是RowContains(),最后是这一行:

if (row[i] == value)

这个函数花费大部分时间是正常的,因为它们工作繁重,但我想知道是否有更好的方法来做到这一点。

编辑:

   private static int[] InitalizeRow(int length)
    {
        int[] row = new int[length];
        for (int i = 0; i < length; i++)
            row[i] = -1;
        return row;
    }

【问题讨论】:

  • 如果你要生成“一切”,为什么不直接使用随机播放?
  • 不知道是什么,我去查一下。
  • 肯定有更好的方法来做GenerateRow 方法所做的事情。但是关于这种方法的问题是什么?还是AreAllNumbersGenerated?后者是否用于您程序中的其他内容?
  • 1) 听哈罗德的。如果您无论如何都要生成所有数字,请生成它们并执行随机播放。 2)如果没有,使用 HashSet 在 O(1) 时间内进行查找。这个号码已经生成了吗? 查看哈希集。 3) 但实际上,随机播放。即使使用 HashSet 提供的快速查找,您最终还是会花费大量时间与已经生成的值发生冲突。

标签: c# arrays performance


【解决方案1】:

对数组中的每个数字进行线性扫描是在浪费大量工作。相反,您应该做的是遍历数组一次并开始观察您看到的最后一个数字。如果您看到任何间隙,请返回 false。如果到达数组的末尾,则返回 true。这是 O(N) 而不是像现在这样的 O(N^2)。

像这样:

public static bool AreAllNumbersGenerated(int[] row)
{
    var last = 0;
    return row.Aggregate(true, (l, r) => l && r == last++);
}

您可以进行一些不会影响大 O 复杂性的额外优化,例如使用适当的循环并提前退出。

编辑:我在这里假设您希望这些项目按顺序发生。如果不是这种情况,您可以轻松地将数组转换为在 O(N) 中使用 ~O(1) 查找的结构,然后在 O(N) 中进行检查。对于大量输入,这仍然比您上面的 O(N^2) 方法更好

像这样:

public static bool AreAllNumbersGenerated2(int[] row)
{
    var available = row.ToDictionary(x => x);
    return Enumerable.Range(0, row.Length).All(i => available.ContainsKey(i));
}

编辑:从我在 cmets 中看到的情况来看,这段代码的目标似乎是用随机顺序的连续数字序列填充数组。我没有意识到这是目标,如果是这样的话,洗牌肯定比一遍又一遍地重新尝试生成要好。但是,比生成数组然后改组(同样,对于足够大的输入)更好的是通过将每个数字分配给随机采样的索引而不进行替换来简单地生成数组。 LINQ 并不容易,但您可以弄清楚。

【讨论】:

  • 您如何在没有更换的情况下进行采样?当然我们可以再次洗牌和连续整数数组,但这已经是结果了。
  • @harold 您首先将所有索引放入恒定时间查找中,并将计数器初始化为零。当计数器
  • 好吧,这行得通,但这听起来比洗牌更糟糕。
【解决方案2】:

正如 cmets 中的其他人所指出的,最有效的方法是生成一个包含您想要的值的数组并对其进行洗牌:

public static void Shuffle<T>(T[] arr, Random r)
{
    for (int i = arr.Length - 1; i > 0; --i)
    {
        int j = r.Next(i + 1);
        T tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

public static int[] Generate(int length, Random r)
{
    var arr = new int[length];
    for(int i = 0; i < length; ++i)
    {
        arr[i] = i;
    }
    Shuffle(arr, r);
    return arr;
}

然后

var arr = Generate(10, new Random());
foreach (int i in arr) { Console.WriteLine(i); }

【讨论】:

  • 我试过了,效果和我找到的其他方法一样,但它更适合我想做的事情。
  • +1,但这会产生有偏差的随机播放。您可以通过将 r.Next(arr.Length) 替换为 r.Next(i) 我认为将其变成公平的 Fisher-Yates 洗牌。
【解决方案3】:

感谢 @sous2817 评论,我找到了一个非常适合我的方法。

public static int[] GetRandomNumbers(int count, Random RNG)
    {
        HashSet<int> randomNumbers = new HashSet<int>();
        for (int i = 0; i < count; i++)
            while (!randomNumbers.Add(RNG.Next(count))) ;
        return randomNumbers.ToArray();
    }

这比我正在做的工作快约 10 倍,并且与我的程序的其余部分一起工作得很好。我需要代码是“线性的”,所以其他解决方案会弄乱我的程序。无论如何,感谢所有帮助过的人:)

【讨论】:

  • 你可以将for循环for (int i = 0; i &lt; count; i++) while (!randomNumbers.Add(RNG.Next(count))) ;简化为while (randomNumbers.Count &lt; count &amp;&amp; !randomNumbers.Add(RNG.Next(count)));
【解决方案4】:

您可以在列表中生成可能的值并在随机位置取值,而不是随机播放。这样,您甚至可以在生成整行之前开始使用这些项目:

    IEnumerable<int> RandomRange(int count) {
        var random = new Random();
        var list = new int[count];
        for (int i = 0; i < count; i++) list[i] = i;

        while (count > 0) {
            var i = random.Next(count);
            yield return list[i] ;
            list[i] = list[--count];
        }
    }

示例使用:

foreach(var item in RandomRange(10))
    // ...

【讨论】:

  • 这会将原本是 O(n) 的操作变成 O(n^2) 的操作。
  • @Servy 你如何得到 O(n^2)?在随机位置从列表中获取一个项目是 O(1),因此生成列表 + O(n) 将是 O(n) 以从中获取所有项目。
  • O(n) to take all the items from it 你的假设是错误的。使用您的代码获取 one 项是 O(n);这样做 n 次使它成为 O(n^2)。
  • 你不能 RemoveAt(i) 然后,你必须与最后一个项目交换,然后删除最后一个位置。如果你无论如何都要生成整个东西,那么懒惰也是没有意义的。如果你真的愿意,你也可以用随机播放来做同样的事情。
  • @harold 没有理由删除最后一项,真的。离开它更容易。这样做也意味着您不需要复制整个集合,因为您不需要列表。
猜你喜欢
  • 2013-08-24
  • 1970-01-01
  • 2014-11-29
  • 2011-02-15
  • 1970-01-01
  • 2019-10-31
  • 2016-03-13
  • 1970-01-01
  • 2014-03-01
相关资源
最近更新 更多