算法图解读书笔记

二分法查找

  • 对于区间[a,b]上连续不断且f(a)·f(b)<0的函数y=f(x),通过不断地把函数f(x)的零点所在的区间一分为二,使区间的两个端点逐步逼近零点。

  • 适用于提高大列表查询速度,对于包含n个元素的列表,时间复杂度为:O(log2n)。

  • 列表必须有序,并且当列表中存在多个相同元素时并非返回列表中第一次出现元素,不同实现算法获取到元素索引也不一定相同。
  • 代码实现1

      /// <summary>
      /// 递归实现元素查找
      /// <para>集合长度不变,左右查找边界移动。</para>
      /// <para>已经做集合有序性检查,必须升序,否则返回-1</para>
      /// <para>集合中存在查找元素时返回索引,否则返回-1。</para>
      /// </summary>
      /// <param name="list">查找集合</param>
      /// <param name="t">待查找元素</param>
      /// <param name="left">查找左边界</param>
      /// <param name="right">查找右边界</param>
      /// <returns></returns>
      static int FindIndexByBisection(List<int> list, int t, int left, int right)
      {
          /*保证集合有序*/
          if (list[left] > list[right])
              return -1;
    
          /*集合中存在的元素一定在最小值与最大值之间*/
          if (t < list[left] || t > list[right])
              return -1;
    
          /*左右边界已经首次靠近,但是两边界所在元素均不等于待查找元素,直接返回*/
          if (right - left == 1 && list[left] != t && list[right] != t)
              return -1;
    
          int midIndex = (left + right) / 2;
          int midValue = list[midIndex];
    
          if (t < midValue)
              return FindIndexByBisection(list, t, left, midIndex);
          else if (t > midValue)
              return FindIndexByBisection(list, t, midIndex, right);
          else
              return midIndex;
      }
  • 代码实现2

      /// <summary>
      /// 递归实现元素查找
      /// <para>集合长度变化,左右查找边界固定。</para>
      /// <para>已经做集合有序性检查,必须升序,否则返回-1。</para>
      /// <para>集合中存在查找元素时返回索引,否则返回-1。</para>
      /// </summary>
      /// <param name="list">查找集合</param>
      /// <param name="t">待查找元素</param>
      /// <param name="index">待查找元素索引</param>
      static void FindIndexByBisection(List<int> list, int t, ref int index)
      {
          /*初始值处理,防止调用方法中设置了错误默认值*/
    
          int count = list.Count;
          int midIndex = count / 2;
          int midValue = list[midIndex];
    
          /*找到元素立即返回*/
          if (t == midValue)
          {
              index += 1;
              return;
          }
    
          /*未找到元素,且集合长度变为1返回-1*/
          if (list.Count == 1)
          {
              index = -1;
              return;
          }
    
          /*未找到元素,集合长度大于1,但集合无序返回-1*/
          if (list[count - 2] > list[count - 1])
          {
              index = -1;
              return;
          }
    
          List<int> midList = list.Take(midIndex).ToList();
          if (t > midValue)
          {
              index += midIndex;//元素比集合中间位置元素大时将中间元素索引累加上去
              midList = list.Skip(midIndex).ToList();
          }
          FindIndexByBisection(midList, t, ref index);
      }

算法效率

  • 大O表示法表示最糟糕情况下程序的运行时间。
  • 常见大O运行时间
    • O(log n) 对数时间,二分查找。
    • O(n) 线性时间,简单查找。
    • O(n * log n) 快速排序算法。
    • O(n*n) 选择排序算法。
    • O(n!) 旅行商问题。
  • 算法的速度指的并非时间,而是操作数的增速。
  • 谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加。
  • 大O表示法忽略常量是由于不同算法间效率相差几个数量级,而对于效率相近的算法常量有时候事关重大。

选择排序

  • 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
  • 时间复杂度O(n*n)。
  • 代码实现1

      /// <summary>
      /// 选择排序
      /// <para>时间复杂度o(n^2)</para>
      /// </summary>
      /// <param name="list"></param>
      /// <returns></returns>
      static List<int> SelectionSort(List<int> list)
      {
          var result = new List<int>();
    
          for (var i = 0; i < list.Count; i++)
          {
              var minValue = list[i];
              for (var j = i; j < list.Count; j++)
              {
                  if (list[j] < minValue)
                      minValue = list[j];
              }
              result.Add(minValue);
          }
          return result;
      }

    递归

  • 原则 如果使用循环,代码的性能可能更高;如果使用递归,程序可能更容易理解。如何选择要看什么对你来说更重要。
  • 组成
    • 基线条件指什么条件下结束函数自我调用,避免无限循环;
    • 递归条件指函数调用自己。

快速排序

  • 分而治之策略
    • 找出基线条件,条件尽可能简单。
    • 不断将问题分解,直到符合基线条件。
  • 快速排序原理
    • 从数组中随机地选择一个元素作为基准值;
    • 找出比基准值小以及比基准值大的元素,将数组分区;
      • 小于基准值的元素构成的子数组;
      • 基准值;
      • 大于基准值的元素构成的子数组。
    • 对两个子数组进行快速排序。
  • 时间复杂度O(n log n)。
  • 代码实现1

      /// <summary>
      /// 快速排序
      /// </summary>
      /// <param name="list"></param>
      /// <returns></returns>
      static List<int> QuickSort(List<int> list)
      {
          /*基准条件:当列表为空或者只存在一个元素时一定有序,返回列表*/
          if (list?.Count <= 1)
              return list;
    
          var midIndex = list.Count / 2;
          var midValue = list[midIndex];//基准值
          var lessList = new List<int>();//左列表
          var greaterList = new List<int>();//右列表
          for (int i = 1; i < list.Count; i++)
          {
              if (list[i] <= midValue)
                  lessList.Add(list[i]);
              else
                  greaterList.Add(list[i]);
          }
    
          lessList = QuickSort(lessList);
          greaterList = QuickSort(greaterList);
    
          var result = new List<int>();
          result.AddRange(lessList);
          result.Add(midValue);
          result.AddRange(greaterList);
          return result;
      }

散列表

  • 散列函数
    • 总是将相同的输入映射到相同的索引;
    • 将不同的输入映射到不同的索引;
    • 知道数组大小,只返回有效索引。
  • 填装因子散列表包含的元素数/位置总数。
  • 冲突
    • 场景给两个键分配的位置相同。
    • 性能
      • 数组中冲突位置存放一个链表,而将冲突键存到链表中。可以避免冲突性能较低。
      • 保证较低的填装因子(<=1)。
  • 时间复杂度O(1)。
上一篇:UOJ#450. 【集训队作业2018】复读机 排列组合 生成函数 单位根反演


下一篇:CSS基础篇---文本属性(5)