算法简介

算法定义

算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。

算法特征

  • 输入输出:算法具有零个或者多个输入,最少有一个或者多个输出。
  • 有穷性:指算法的每一个步骤都有确定的含义,不会出现二义性。
  • 可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。

算法设计的要求

  • 正确性:算法的正确性是指算法至少具有输入、输出和加工处理无歧义性、能正确反应问题的需求、能够得到问题的正确答案。
  • 易读性:算法设计的另一目的是为了方便阅读、理解和交流。
  • 强壮性:当输入数据不合法时,算法也能做出相关的处理,而不是产生异常或者莫名其妙的结果。
  • 高效率

二分查找法

二分查找法输入必须是一个有序序列,如果要查找的位置在列表中,返回位置,否则返回 null。每次取中间的数字作为基准值,如果比基准值大,新的基准值为右面的“中间值”,否则为左边的“中间值”。

运行时间

选择运行效率最高的算法,以最大地减少运行时间或占用空间。二分查找法的时间复杂度为 log(时间)。

大 O 表示法

仅知道算法需要多少时间才能执行完毕还不够,还需要知道运行时间如何随列表增长而增加。这才是大 O 表示法的真正用武之地。大 O 表示法让你能比较操作数,它指出了运行时间的递增。大 0 表示法指出了最坏情况下的运行时间。

推导方式

  • 用常数 1 代替运行时间中的所有加法常量。
  • 在修改后的运行次数函数中,只保留最高阶乘。
  • 如果最高阶存在且不是 1,则去除与这个项目相乘的常数。

得到的就是大 O 阶。

算法时间复杂度总结

1604657195-RVqzXY-image.png

选择排序

内存的工作原理

需要储存数据时,请求计算机提供存储空间,计算机给你一个地址。

数组和链表

额外请求的位置可能用不上,这将浪费内存。

数组和链表的运行时间

数组 链表
读取 O(1) O(n)
插入 O(n) O(1)

使用链表插入元素更简单,如果没有足够的空间需要将数组复制到其他地方。删除元素,链表也是也是更好的选择,只需要更改前一个元素的指向的指针,把删除元素释放即可。而数值在删除元素后,必须将后面的元素都向前移动。使用数组访问元素更方便,因为它支持随机访问,而链表只能顺序访问。

选择排序

每一次找出最大值放到新的序列中,直到所有的值都添加到新的序列。因为大 O表示法会忽略诸如 1/2 这样的常数,所以这个算法的时间复杂度为 O(n)。

递归

如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。如何选择看什么对你更重要。

基线条件和递归条件

每一个递归函数都有两个部分:基线条件和递归条件。递归条件就是自己调用自己,而基线条件是指函数不再调用自己,从而避免形成无限循环。

调用另一个函数时,当前函数暂停并处于未完成状态。

虽然栈很方便,但是要付出代价:储存详尽的信息可能占用大量的内存。每个函数调用都要占用一定的内存,如果栈很高,就意味着存储了大量函数调用的信息。在这种情况下,你有两种选择:

  • 重新编写代码,转而使用循环。
  • 使用尾递归。

快速排序

D&C 算法解决问题的过程:

  • 找出基线条件,这种条件必须尽可能简单。
  • 不断将问题分解(或者说缩小规模),直到找到符合基线条件。

快速排序算法

首先,从数组中选择一个元素,这个元素作为基准值。接下来,找出比基准值小的元素以及比基准值大的元素。这个过程被称为分区。对这些分区递归进行快速排序。

该算法的时间复杂度为O(n logn)。

散列表

散列函数必须满足一下要求:

  • 它必须是一致的。
  • 将不同的输入映射到不同的数字。

应用

  • 模拟映射关系
  • 防止重复
  • 缓存、记住数据,以免服务器再通过处理来生成它们。

冲突

一个简单的解决冲突的方法就是在相同的位置创建链表,如果链表很长散列表的速度将急速下降。

性能

在使用散列表时,避开最糟糕的情况很重要。避免冲突有以下需求:

  • 较低的填充因子。
  • 良好的散列函数。

填充因子

填充因子 = 散列表包含的元素数 / 位置总数

散列因子越低,发生冲突的可能性就越小。一旦填装因子大于 0.7,就调整散列表的长度。

良好的散列函数

SHA 函数

参考

【1】算法图解
【2】大话数据结构
【3】2021 春招冲刺攻略