• 数据搜索算法


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

    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的结果是一个索引号,而不是具体的数值

  • 相关阅读:
    程序员都遇到过哪些误解?
    云原生系列5 容器化日志之EFK
    云原生系列4 批量定时更新本地代码库
    云原生系列3 pod核心字段
    云原生系列2 部署你的第一个k8s应用
    云原生系列1 pod基础
    项目总结二:使用分布式存储读写分离功能应要注意的问题
    项目总结一:HttpClient DelegatingHandler管道扩展 生命周期问题
    Java 反编译工具的使用与对比分析
    如何使用 Github Actions 自动抓取每日必应壁纸?
  • 原文地址:https://www.cnblogs.com/chenxizhang/p/1441377.html
Copyright © 2020-2023  润新知