一个关于字典查找引发的思考——BinarySearch
2012-08-02 12:20 xiashengwang 阅读(1983) 评论(0) 编辑 收藏 举报最近在一个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