数据搜索算法

排序和搜索是数据结构和算法学习中的两个最基本的操作。关于排序,我在上一篇已经做了比较详细的介绍,请参考

http://www.cnblogs.com/chenxizhang/archive/2009/04/22/1441209.html

 

这一篇我们来关注一下搜索。我们同样把目光放在Array这个最基础的数据类型上面。我们从几个实例来讲解怎么利用.NET的内部机制实现检索

1. Array.Exists

这个方法是判断是否在指定数组中存在某个成员。让我们来看看这个方法的定义

image

该方法返回一个bool值,这很好理解。它的第一个参数是一个Array,这也很好理解。(就是我们要搜索的数据源),而第二个参数是一个所谓的Predicate类型。我们展开来看一下这个东西吧

image

这是一个委托。也就是说它是一个指针,需要我们告诉它如何判断数据存在的依据。那么怎么给出这个参数呢?

 

传统的做法,我们准备一个方法,这个方法是满足该委托的签名的(有一个参数,而且返回一个bool),然后通过该委托去调用该方法。(假设我们这里判断存在的依据是该数字等于2)

static bool MatchInt(int input)
{
    return input == 2;
}

然后,在Exists方法中使用委托来调用这个MatchInt方法去进行判断。(有两种写法)

static void Main(string[] args)
{
    int[] numbers = new int[] { 3, 7, 2, 4, 10, 1 };
    Console.WriteLine(Array.Exists<int>(numbers, new Predicate<int>(MatchInt)));//这是原始写法
    Console.WriteLine(Array.Exists<int>(numbers, MatchInt));//这是上面一句代码的简写

    Console.Read();

}

 

从上面的代码可以看出,为了做这个判断,我们专门写了一个方法,这在很多时候会增加代码阅读的麻烦,因为阅读者需要从一个方法跳到另外一个方法,不断反复。

所以,在C# 2.0中,提出了匿名方法的概念,也就是对于此类不太需要单独封装的地方,可以直接将方法体合并在委托声明中。也就是说,没有必要单独写MatchInt这个方法。

Console.WriteLine(Array.Exists<int>(numbers, delegate(int i) { return i == 2; }));

这一句代码就可以完成所有事情了。其实也很直观。

 

而在最新的C# 3.0中,针对这这种问题,又有了改进,就是所谓的Lambda表达式,大家来看一下代码是如何写的

Console.WriteLine(Array.Exists<int>(numbers, i => i == 2));

你可能一下子还不理解Lambda,但只要大致看一下C# 3.0的新语法例子,其实还是比较通俗的。

image

如果有兴趣的朋友,可以通过IL代码看到,第二种方式和第三种方式其实是一模一样的。匿名方法和Lambda是语言之上的改进,编译的结果还是需要通过delegate来实现的。你也可以说,它们与第一种没有根本区别。

但在事实上,他们确实更加直观和简洁。

2. Array.Find, Array.FindLast

如果我们需要搜索一个数组中的某个元素,而不光是判断它是否存在。我们大致的写法如下

Console.WriteLine(Array.Find<int>(numbers, i => i == 2));

注意,如果找到了,则返回2这个数值。Find方法是找到一个即停止。而如果要找最后一个匹配的值,就需要用FindLast

 

3. Array.FindAll

这个方法是搜索所有匹配的数据,返回一个数组。

int[] foundnumbers = Array.FindAll<int>(numbers, i => i % 2 == 0);//搜索所有的偶数
foreach (var item in foundnumbers)
{
    Console.WriteLine(item);
}

有意思的是,上面的代码可以简写为下面一句代码

Array.ForEach<int>(Array.FindAll<int>(numbers, i => i % 2 == 0), i => Console.WriteLine(i));

 

4. Array.FindIndex,Array.FindLastIndex

这个方法是检索相应的值在数组中的索引号

Console.WriteLine(Array.FindIndex<int>(numbers, i => i == 1));

Console.WriteLine(Array.FindLastIndex<int>(numbers, i => i == 1));

 

5. Array.BinarySearch

上面的Find方法,基本上都是基于顺序的。也就是,如果某个数字很不凑巧在最后面,那么搜索程序就不得不将每个数字都检查一次,比较他们。这样,在很多时候效率是不够高的。当然,如果数据本身没有规律,是随机分布的,这也是无法避免的。

如果说数据本身有顺序,那么就可以利用二进制搜索来提高速度。二进制搜索其实是一个折半搜索算法。也就是说,既然数据本身有顺序,就可以不要一个一个比较,而是可以在某个范围内比较

image

image

从这个原理可以知道,二进制搜索是依赖数据本身排序的。事实上,如果数据没有排序,则该方法也不会出错,但是会返回一个负数或者一些奇怪的结果。

我们用例子来比较一下二进制搜索和顺序搜索的差别

static void Main(string[] args)
  {

      //准备一个数组,随机填充10000000个数字
      int[] numbers = new int[10000000];
      Random rnd = new Random();
      for (int i = 0; i < numbers.Length; i++)
      {
          numbers[i] = rnd.Next(10000000);
      }

      //采用顺序搜索的方式,查找里面100这个数值(如果运气比较好,正好有100的话)
      Stopwatch watch = Stopwatch.StartNew();
      Console.WriteLine(Array.Find<int>(numbers, i => i == 100));
      watch.Stop();
      Console.WriteLine("顺序搜索使用的时间为:{0}毫秒", watch.ElapsedMilliseconds);

      //采用二进制搜索的方式,查找里面100这个数值
      watch.Reset();
      watch.Start();
      Array.Sort<int>(numbers);//先排序
      watch.Stop();
      Console.WriteLine("排序的时间:{0}毫秒", watch.ElapsedMilliseconds);

      watch.Reset();
      watch.Start();
      Console.WriteLine("该数字所在的索引号是:{0}",Array.BinarySearch<int>(numbers, 100));
      watch.Stop();
      Console.WriteLine("二进制搜索的时间:{0}毫秒", watch.ElapsedMilliseconds);

      Console.Read();

  }

image

我们发现,二进制搜索几乎不需要时间,这太神奇了。但是我们也发现排序是要花很长时间的。

当然,如果我们需要多次在一个数组中去频繁地检索,那么一次排序的代价相比较多次搜索而言,可能是微不足道的

另外,需要知道的是,BinarySearch的结果是一个索引号,而不是具体的数值

posted @ 2009-04-22 15:12  陈希章  阅读(3273)  评论(0编辑  收藏  举报