zoukankan      html  css  js  c++  java
  • 一个关于字典查找引发的思考——BinarySearch

      最近在一个e文网站的提问区看到一个非常有意思的问题,内容如下:

    有几组信号范围(range),每个范围对应一个位置(Position):

    Signal Strenth             Position
    0-9                            1
    10-19                          2
    20-29                          3

    要求很简单,就是比如输入一个15,找到它的Position为2。但要求不能用IF语句进行简单的判定,最好用类似于字典的查找。

    看见这个问题,你脑海中有什么样的方案?Dictionary?

    在实际工作中还是经常遇到类似的要求。比如:0~5级为小菜,6~10级为大鸟,11~15为牛人,16~20为专家,20~30为大师。

    我们来看看这些回答者的思路吧,往往我们能从中受到一定的启发。

    1 ,方法一

    int position = signalStrength / 10 + 1;

    仅针对当前这个问题,用一个小巧的函数,比类似Dictionary会高效太多。

    2,方法二

    Dictionary<int, int> values;
    
    values = new Dictionary<int, int>();
    
    values[0] = 1;
    values[1] = 1;
    ...
    values[29] = 3;

    然后再查找这个字典:

    Console.WriteLine(values[15].ToString());

    3,方法三

    两个Dictionary嵌套,后面一个Dictionary是范围

    PS:这种方式感觉很别扭,还要额外去遍历Dictionary才能找到对应的key

    dictionary<string,dictionary<int,int>>

    4,方法四

    定义一个简单的List,每一个元素是一个自定义范围类

    public class Controller
    {
       List m_positions = new List();
    
       public void LoadPositions()
       {
          m_positions.Add(new Range(0, 9));
          m_positions.Add(new Range(10, 19));
          m_positions.Add(new Range(20, 29));
       }
    
       public int GetPosition (int signal)
       {
          Range range = m_positions.Single(a => IsBetween(signal, a.Min, a.Max));
    
          return m_positions.IndexOf(range);
       }
    
       private static bool IsBetween (int target, int min, int max)
       {
          return target>=min && target<=max;
       }
    }
    public class Range
    {
       public Range(int min, int max)
       {
          this.Min = min;
          this.Max = max;
       }
    
       public int Min
       {
          get;
          private set;
       }
    
       public int Max
       {
          get;
          private set;
       }
    }

    PS:这种方式可读性很好,但查询效率会很低。

    5,方法五

    PS:这个效率很高,用了一个折半查找。

    class SignalStrengthPositionMapper
    {
        private static readonly int[] signalStrength = { Int32.MinValue, 0, 5, 11, 15, 20, 27, 35 };
        public static int GetPosition(int strength)
        {
            return StrengthSearch(0, signalStrength.Length, strength);
        }
    
        // modified binary search
        private static int StrengthSearch(int start, int end, int strength)
        {
            int mid = 0;
            while (start <= end)
            {
                mid = (start + end) / 2;
    
                if (strength >= signalStrength[mid])         // lower bound check
                {
                    start = mid + 1;
                    if (strength < signalStrength[start])    // upper bound check
                        return mid;
                }
                else if (strength < signalStrength[mid])     // upper bound check
                {
                    end = mid - 1;
                    if (strength >= signalStrength[end])     // lower bound check
                        return mid;
                }
            }
            return 0;
        }
    }

    6,方法六

    这个方法,是提问者采纳的方法。

    // Definition of ranges
    int[] ranges = new int[] { 9, 19, 29 };
    
    // Lookup
    int position = Array.BinarySearch(ranges, 15);
    if (position < 0)
        position = ~position;
    
    // Definition of range names
    string[] names = new string[] { "home", "street", "city", "far away" };
    
    Console.WriteLine("Position is: {0}", names[position]);

    该方法采用了FCL提供的BinarySearch方法,它实际上是一个二分查找算法,要求被查找的序列是有序的。如果不是有序,可事先对它进行排序后再查找。

    7,发挥你的才能,欢迎提出不同的观点。

    附:关于BinarySearch方法

    原型如下

    public static int BinarySearch<T>(
        T[] array,
        T value,
        IComparer<T> comparer
    )

    返回值(摘自MSDN):

    • 如果找到 value,则为指定 array 中的指定 value 的索引。 
    • 如果找不到 value 且 value 小于 array 中的一个或多个元素,则为一个负数,该负数是大于 value 的第一个元素的索引的按位求补。 如果找不到 value 且 value 大于 array 中的任何元素,则为一个负数,该负数是(最后一个元素的索引加 1)的按位求补。

    PS:对于这里的按位求补(bitwise complement ),和我们传统意义上的求补码不是一回事,它实际就是对索引值取反,C#中就是~运算。所以,如果我们判断返回的是一个负数,就对这个负数再求一次反,得到的就是原索引值。

    看看下面的测试代码:

                int[] range = { 9, 19, 29 };
    
                int index1 = Array.BinarySearch(range, 7);
                int index2 = Array.BinarySearch(range, 9);
                int index3 = Array.BinarySearch(range, 15);
                int index4 = Array.BinarySearch(range, 30);
                if (index1 < 0) {
                     Console.WriteLine("index1 is negative:{0}", index1);
                     index1 = ~index1;
                }
                if (index2 < 0) { 
                     Console.WriteLine("index2 is negative:{0}", index2);
                     index2 = ~index2;
                }
                if (index3 < 0) { 
                     Console.WriteLine("index3 is negative:{0}", index3);
                     index3 = ~index3;
                }
                if (index4 < 0) { 
                    Console.WriteLine("index4 is negative:{0}", index4);
                    index4 = ~index4; 
                }
    
                Console.WriteLine("index1 is {0}", index1);
                Console.WriteLine("index2 is {0}", index2);
                Console.WriteLine("index3 is {0}", index3);
                Console.WriteLine("index4 is {0}", index4);

    输出结果:

    index1 is negative:-1
    index3 is negative:-2
    index4 is negative:-4
    index1 is 0
    index2 is 0
    index3 is 1
    index4 is 3
  • 相关阅读:
    python测试开发django-39.xadmin详情页面布局form_layout
    Linux学习20-nohup挂后台启动django
    python测试开发django-38.多对多(ManyToManyField)查询
    python测试开发django-37.外键(ForeignKey)查询
    因子分解机模型简介
    Social regularizations
    MathType插入带序号公式的两种方法
    通俗解释遗传算法及其Matlab实现
    矩阵中路径数目问题
    Word绘制跨行表格
  • 原文地址:https://www.cnblogs.com/xiashengwang/p/2619711.html
Copyright © 2011-2022 走看看