【问题标题】:How to save CPU cycles when searching for a value in a sorted list?在排序列表中搜索值时如何节省 CPU 周期?
【发布时间】:2016-10-20 22:03:41
【问题描述】:

CodinGame学习平台,C#教程中作为示例使用的问题之一是:

这个练习的目的是检查一个数字在一个 数组。

规格:项目是按升序排列的整数。 该数组最多可包含 100 万个项目。该数组永远不会是null。 实现方法 boolean Answer.Exists(int[] ints, int k) 以便 如果k 属于ints,则返回true,否则该方法应 返回false

重要提示:尽可能节省 CPU 周期。

例子:

int[] ints = {-9, 14, 37, 102};

Answer.Exists(ints, 102) 返回true
Answer.Exists(ints, 36) 返回false

我的建议是这样做:

using System;
using System.IO;

public class Answer
{
    public static bool Exists(int[] ints, int k)
    {
        foreach (var i in ints)
        {
            if (i == k)
            {
                return true;
            }

            if (i > k)
            {
                return false;
            }
        }

        return false;
    }
}

测试结果是:

  • ✔ 该解决方案适用于“小”数组(200 分) - 问题解决
  • ✔ 该解决方案适用于空数组(50 分) - 可靠性
  • ✘ 该解决方案在合理的时间内适用于一百万个项目(700 分) - 问题解决

我不明白最后一点。看来代码可能比我建议的更优化。

如何优化这段代码? 二分搜索是一个实际的解决方案(假设数组中的值已经排序),还是我错过了一些更简单的方法?

【问题讨论】:

  • 看来练习的重点是推导出自己的算法。不然你用BinarySearch学到什么(除了怎么用)?
  • 顺便说一句:if (i > k) return false; 并没有太大的改进。它可以让您避免循环迭代 i>k,但它还为每次迭代添加了额外的比较操作。从好的方面来说,CPU 分支预测器可能会以非常高的准确度预测该比较的结果。

标签: c# algorithm optimization binary-search


【解决方案1】:

是的,我认为二进制搜索O(log(N))complexity v.O(N)complexity 是解决方案:

   public static bool Exists(int[] ints, int k) {
     return Array.BinarySearch(ints, k) >= 0;
   }

因为如果项目 (k) 已找到,则 Array.BinarySearch 返回 非负 值:

https://msdn.microsoft.com/en-us/library/2cy9f6wb(v=vs.110).aspx

返回值类型:System.Int32 中指定值的索引 指定的数组,如果找到值;否则为负数。

【讨论】:

  • 但是,BinarySearch 的使用不会否定练习的目的吗?
  • @IAbstract:看起来可能是这样;在设计解决方案时,我会毫不犹豫地输入Array.BinarySearch。如果在Array 课程未被禁止的情况下进行练习,我也会这样做:练习应该教我们编写真实世界的代码。只有当我应该实现一个算法时,我才会手动进行二进制搜索。
  • Here is the source of Array.BinarySearch from MSDN.。它有很多其他错误检查/处理的东西,但如果你想从头开始实现它,你可以看到从第 931 行开始的二进制搜索算法。
【解决方案2】:

这是有序数组的快速方法

public static class Answer
{
    public static bool Exists( int[] ints, int k )
    {
        var lower = 0;
        var upper = ints.Length - 1;

        if ( k < ints[lower] || k > ints[upper] ) return false;
        if ( k == ints[lower] ) return true;
        if ( k == ints[upper] ) return true;

        do
        {
            var middle = lower + ( upper - lower ) / 2;

            if ( ints[middle] == k ) return true;
            if ( lower == upper ) return false;

            if ( k < ints[middle] )
                upper = Math.Max( lower, middle - 1 );
            else
                lower = Math.Min( upper, middle + 1 );
        } while ( true );
    }
}

在我的 cpu 上花费大约 50 个滴答声(数组中有 90.000.000 个项目)

Sample on dotnetfiddle

【讨论】:

    【解决方案3】:
    class Answer
    {
        public static bool Exists(int[] ints, int k)
        {
            int index = Array.BinarySearch(ints, k);
            if (index > -1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        static void Main(string[] args)
        {
            int[] ints = { -9, 14, 37, 102 };
            Console.WriteLine(Answer.Exists(ints, 14)); // true
            Console.WriteLine(Answer.Exists(ints, 4)); // false
        }
    
    }
    

    【讨论】:

      【解决方案4】:

      显然,该任务打算我们使用the default binary search method 而不是实现一个。我也有点惊讶它在第三次测试中的评估结果。 “解决方案使用标准库执行二分查找(在整数上迭代)”

      这有点令人困惑,他们本可以在代码中提到这一点,而不是花 15 到 20 分钟来解决。这是造成这种混乱的另一个原因。

      这是我为那个问题写的 -> 将数组分成一半并搜索一半 -> 一种更基本的实现方式......

      int half = size/2;
      if( k < ints[half])
      {
          for(int i=0; i < half; i++)
          {
              if( k == ints[i])
              {
                   return true;
              }
          }
      }
      else
      {
          for(int i=half; i < size; i++)
          {
              if( k == ints[i])
              {
                  return true;
              }                
          }
       }
      
      

      【讨论】:

      • 有趣。我见过其他案例(虽然我不记得哪些案例),其中练习似乎建议编写一个基本算法(并给了足够的时间来起草一个),而实际上你应该用一个来回应——包含对 .NET Framework 中的方法的调用的衬里。
      • 我发现 binary search (iterating on ints) sibylic 本身。
      【解决方案5】:
       public static bool Exists(int[] ints, int k)
              {
                  var d = 0;
                  var f = ints.Length - 1;
                  if (d > f) return false;
                  if (k > ints[f] || k < ints[d]) return false;
                  if (k == ints[f] || k == ints[d]) return true;
                  return (BinarySearch(ints, k, d, f) > 0);
              }
      
       public static int BinarySearch(int[] V, int Key, int begin, int end)
              {
                  if (begin > end)
                      return -1;
                  var MidellIndex = (begin + end) / 2;
      
                  if (Key == V[MidellIndex])
                      return MidellIndex;
                  else
                  {
                      if (Key > V[MidellIndex])
                      {
                          begin = MidellIndex + 1;
                          return BinarySearch(V, Key, begin, end);
                      }
                      else
                      {
                          end = MidellIndex - 1;
                          return BinarySearch(V, Key, begin, end);
                      }
                  }
      
              }
      

      【讨论】:

        【解决方案6】:

        通过创建和测试以下递归方法并获得完整点的方式,我看到了所有解决方案:

        public static bool Exists(int[] ints, int k)
            {
        
        
                if (ints.Length > 0 && ints[0] <= k && k <= ints[ints.Length - 1])
                {
                    if (ints[0] == k || ints[ints.Length - 1] == k) return true;
                    return SearchRecursive(ints, k, 0, ints.Length - 1) != -1;
                }
                return false;
            }
        
            private static int SearchRecursive(int[] array, int value, int first, int last)
            {
                int middle = (first + last) / 2;
        
                if (array[middle] == value)
                {
                    return middle;
                }
                else if (first >= last)
                {
                    return -1;
                }
                else if (value < array[middle])
                {
                    return SearchRecursive(array, value, first, middle - 1);
                }
                else
                {
                    return SearchRecursive(array, value, middle + 1, last);
                }
            }
        

        【讨论】:

          【解决方案7】:

          是的,BinarySearch 会比您可以手动编写的大多数算法更快。但是,如果练习的目的是学习如何编写算法,那么您就走在了正确的轨道上。但是,您的算法对if (i &gt; k) 进行了不必要的检查……您为什么需要这个?

          以下是我针对此类简单需求的一般算法。像这样的while 循环比for-loop 的性能略高,并且很容易执行foreach

          public class Answer
          {
              public static bool Exists(int[] ints, int k)
              {
                  var i = 0;
                  var hasValue = false;
          
                  while(i < ints.Length && !hasValue)
                  {
                      hasValue = ints[i] == k;
                      ++i;
                  }
          
                  return hasValue;
              }
          }
          

          【讨论】:

            【解决方案8】:

            如果您试图从中挤出每一盎司的速度...考虑您的数组有 1..100,并且您想要搜索 78。您的算法需要搜索和比较 78 个项目,然后才能找到正确的一。相反,你搜索第一个项目而不在那里,所以你跳转到数组大小/2 并找到 50 怎么样?现在您跳过了 50 次迭代。您知道 78 必须位于数组的上半部分,因此您可以再次将其分成两半并跳转到 75,等等。通过不断地将数组分成两半,您执行的迭代次数要比蛮力方法少得多。

            【讨论】:

            • 你很好地描述了我的回答:o)
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-12
            • 2015-04-23
            • 1970-01-01
            • 2021-10-11
            相关资源
            最近更新 更多