四、查找算法(基础)
一、顺序查找
也称之为线性查找,从线性表的一端顺序扫描,依次将扫描到的结果与给定关键字K想比较,如果相等则查找成功,如果扫描到末尾仍未找到,则扫描失败。
首先,我们查看如下代码:
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Text.RegularExpressions; namespace ConsoleApplication9 { class Program { static void Main(string[] args) { int count = 100000000, key = 0, result = 0 ; int[] array = new int[count]; init(array, count); Console.Write("Please enter the number you need to find:"); string outkey = Console.ReadLine(); if (Regex.IsMatch(outkey, @"^[+-]?\d*[0]?\d*$")) { key = int.Parse(outkey); } else { Console.WriteLine("The number you entered is not corrent.system exits."); return; } TestSequentialSearch seqsearch = new TestSequentialSearch(); //Time test System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); result = seqsearch.Search<int>(array, count, key); stopwatch.Stop(); if (result>0) Console.WriteLine("Number has been found {0}, subscript is {1}",key.ToString(),result.ToString()); else Console.WriteLine("Sorry. number not found {0}",key.ToString()); Console.WriteLine("Time for array Search<T>:" + stopwatch.Elapsed.TotalMilliseconds);//key:99999999 output: 1455.***** } #region Data initialization.display and clear static void init(int[] array, int count) { Random random = new Random(); for (int i = 0; i < count; i++) { array[i] = (i + 1); //array[i] = random.Next(0, count); } } static void display(int[] array) { foreach (var item in array) { Console.WriteLine(item); } } static void clear(int[] array, int count) { for (int i = 0; i < count; i++) { array[i] = 0; } } #endregion } internal class TestSequentialSearch { public int Search<T>(T[] array,int count,T key) { for (int i = 0; i < array.GetUpperBound(0); i++) { if (array[i].Equals(key)) return i; } return -1; } } }
看出来,当我们输入的数越小,循环次数越少,耗时越短。数越大,循环次数越多,耗时越久。从上述代码看来耗时是1455多毫秒。
然后在测试数组的Contains方法,代码如下:
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); TestSequentialSearch seqsearch = new TestSequentialSearch(); //Contains time test stopwatch.Start(); array.Contains(key); stopwatch.Stop(); Console.WriteLine("Time for array contains:" + stopwatch.Elapsed.TotalMilliseconds);//key:99999999 output: 106.*****
为什么Constains会比自己写的查找法快10倍呢?可以扒一扒.net framework的源码看看。
如何查看.net framework,请戳这里
根据断点调试器,可以看出来最终源码里边执行的是 System.Array
public static int IndexOf<T>(T[] array, T value, int startIndex, int count) { if (array==null) { throw new ArgumentNullException("array"); } if (startIndex < 0 || startIndex > array.Length ) { throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); } if (count < 0 || count > array.Length - startIndex) { throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); } Contract.Ensures(Contract.Result<int>() < array.Length); Contract.EndContractBlock(); return EqualityComparer<T>.Default.IndexOf(array, value, startIndex, count); }
最后一句跟踪进去,执行的是:System.Collections.Generic.EqualityComparer
internal override int IndexOf(T[] array, T value, int startIndex, int count) { int endIndex = startIndex + count; if (value == null) { for (int i = startIndex; i < endIndex; i++) { if (array[i] == null) return i; } } else { for (int i = startIndex; i < endIndex; i++) { if (array[i] != null && array[i].Equals(value)) return i; } } return -1; }
看来微软的底层实现也是用顺序查找实现的,至于数组的contains为什么会调用IndexOf,请自行翻阅理解 IEnumberable,ICollection,IList,List,Array,等
推荐:戳这里
那么我们稍微改进一下代码,不使用泛型,使用泛型,内部可能有些装箱之内的操作,会增加时耗。
public int Search(int[] array,int count,int key) { for (int i = 0; i < array.GetUpperBound(0); i++) { if (array[i].Equals(key)) return i; } return -1; }
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); TestSequentialSearch seqsearch = new TestSequentialSearch(); stopwatch.Start(); seqsearch.Search(array, count, key); stopwatch.Stop(); Console.WriteLine("Time for array:" + stopwatch.Elapsed.TotalMilliseconds);//key:99999999 output: 946.*****
相对使用泛型方法来说,已经缩短了三分之一多的时耗了。在根据微软底层的实现,来优化,把GetUpperBound的调用去掉,调用方法会增加CPU的执行指令,而后,还有些临时变量
会在内存划分空间等等,都是耗时的操作.
public int Search(int[] array,int count,int key) { for (int i = 0; i <count; i++) { if (array[i].Equals(key)) return i; } return -1; }
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); TestSequentialSearch seqsearch = new TestSequentialSearch(); stopwatch.Start(); seqsearch.Search(array, count, key); stopwatch.Stop(); Console.WriteLine("Time for array:" + stopwatch.Elapsed.TotalMilliseconds);//key:99999999 output: 414.*****
相对上面的优化,又缩短了一半的时间,可是还达不到微软的效率,但是可以看出自己写的代码和微软底层执行的代码差不多一样了,这是为什么呢??
终于,在一次偶然的机会,调试查看底层代码时,发现有这么一句提示 “ 当前方法的代码已经优化过,无法计算当前表达式的值”。
茅塞顿开,于是使用release编译项目运行,终于达到109毫秒的耗时,和微软底层一样了。
不简单啊,从1千多毫秒优化到1百多毫秒
二、折半查找
public int BinSearch(int[] array,int count,int key) { int upperbound, lowerbound,half; upperbound = count-1; lowerbound = 0; while (lowerbound <= upperbound) { half = (lowerbound + upperbound) / 2; if (array[half] == key) return half; if (array[half] > key) //猜大了 upperbound = half - 1; else //猜小了 lowerbound = half + 1; } return -1; }
需求:假设一组有序的数据,10个元素,值为1-10. 那么下标就从0-9。我们需要找此数据中是否包含元素值为7。如果包含返回下标,否则返回-1
伪步骤:
1、0-9 折半 下标为4 值为5 猜小了 (下标为4都值都比需要查找的值小,那么4和之前的就不需要比较了)
2、5-9 折半 下标为7 值为8 猜大了 (可以看出当前数肯定在5-6之间了,比下标为4的大,比下标为7的小)
3、5-6 折半 下标为5 值为6 猜小了 (肯定是6了)
4、7-7 折半 下标为6 值为7 相等 (返回下标6)
实现步骤:(经过上续伪代码,可以总结代码的实现步骤)
1、首先需要定义3个变量,一个是下限,一个是上限,一个是折半的下标,并且给下限和上限设置初始值(这里上限是9,下限是0)
2、编写循环,这里使用while比较合适,条件是下限小于等于上限(一旦下限都比上限大了,那么可以肯定值不在这组数据中了)
3、循环体中第一步就需要折半下标,(下限+上限)/2
4、在用折半的下标值和需要查找的值进行比较,相等则直接返回当前下标
5、如果折半的值比查找值小,那么是猜小了,下限应该在折半的下标基础上加1位
6、如果折半的值比查找值大,那么是猜大了,上限应该在折半的下标基础上减1位
采用折半查找法和顺序查找法进行耗时比较,100000000这么多个数中查找99999999.顺序查找耗时是102毫秒左右,而折半查找法耗时才0.47毫秒
上述折半算法还可以演变为递归折半法:
public int ReBinSearch(int[] array, int upperbound, int lowerbound, int key) { if (lowerbound>upperbound) return -1; //终止条件 terminate condition int half = (lowerbound + upperbound) / 2; if (array[half] == key) return half; if (array[half] > key) return ReBinSearch(array, half - 1, lowerbound, key); else return ReBinSearch(array, upperbound, half + 1, key); }
调用的关键性代码:
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); TestSequentialSearch seqsearch = new TestSequentialSearch(); Algorithm algorithm = new Algorithm(); stopwatch.Start(); result =seqsearch.ReBinSearch(array, count-1,0, key); stopwatch.Stop(); if (result>=0) Console.WriteLine("find number."); else Console.WriteLine("not find."); Console.WriteLine("Time for array:" + stopwatch.Elapsed.TotalMilliseconds);//key:99999999 output:0.4802
当然,FCL中Array.BinarySearch 底层实现同样用的是折半查找法,但是效率比自己定制的高,扒开源码一看,里面有些操作用了位运算,等后面了解深入一点
在重头扒源码优化。现在看不懂。。。
总之,有内置方法就别用用户定制方法。

浙公网安备 33010602011771号