今天分享的是我结合最近学习的《算法图解》 的心得,以及在工作过程中意识到算法的重要性
目录
1.数据结构:数组、链表、散列表、栈、队列、图
2.算法:二分查找法、快速排序(分而治之)、递归、广度优先搜索、狄克斯特拉算法
3.工具:大O表示法
正文
1.大O表示法
大O表示法是一种用于表示算法速度的方法,一般是指算法最糟糕情况下的速度
算法运行时间是从其增速的角度度量的,大O表示法及表示算法的增速趋势
下面按从快到慢的顺序列出了经常会遇到的5种大O运行时间。
O(log n),也叫对数时间,这样的算法包括二分查找。
O(n),也叫线性时间,这样的算法包括简单查找。
O(n * log n),例如快速排序——一种速度较快的排序算法。
O(n 2 ),例如选择排序——一种速度较慢的排序算法。
O(n!),例如旅行商问题的解决方案——一种非常慢的算法。
2.链表、数组、链表、散列表、栈、队列、图
链表的每个元素都存储了下一个元素的地址,从而使一系列随机的内存地址串在一起
这犹如寻宝游戏。你前往第一个地址,那里有一张纸条写着“下一个元素的地址为123”。因
此,你前往地址123,那里又有一张纸条,写着“下一个元素的地址为847”,以此类推。在链表
中添加元素很容易:只需将其放入内存,并将其地址存储到前一个元素中。
使用链表时,根本就不需要移动元素。这还可避免另一个问题。假设你与五位朋友去看一部
很火的电影。你们六人想坐在一起,但看电影的人较多,没有六个在一起的座位。使用数组时有
时就会遇到这样的情况。假设你要为数组分配10 000个位置,内存中有10 000个位置,但不都靠
在一起。在这种情况下,你将无法为该数组分配内存!链表相当于说“我们分开来坐”,因此,
只要有足够的内存空间,就能为链表分配内存。
组与此不同:你知道其中每个元素的地址。例如,假设有一个数组,它包含五个元素,起
始地址为00,那么元素#5的地址是多少呢
、
只需执行简单的数学运算就知道:04。需要随机地读取元素时,数组的效率很高,因为可迅
速找到数组的任何元素。在链表中,元素并非靠在一起的,你无法迅速计算出第五个元素的内存
地址,而必须先访问第一个元素以获取第二个元素的地址,再访问第二个元素以获取第三个元素
的地址,以此类推,直到访问第五个元素。
栈有两种操作:压入和弹出 这种数据结构称为栈。栈是一种简单的数据结构
散列函数“将输入映射到数字”
散列函数总是将同样的输入映射到相同的索引。
散列函数将不同的输入映射到不同的索引
散列函数知道数组有多大,只返回有效的索引。
散列表的填装因子很容易计算
散列表使用数组来存储数据,因此你需要计算数组中被占用的位置数。例如,下述散列表的
填装因子为2/5,即0.4。
图由节点和边组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。
队列的工作原理与现实生活中的队列完全相同。
假设你与朋友一起在公交车站排队,如果你排在他前
面,你将先上车。队列的工作原理与此相同。队列类
似于栈,你不能随机地访问队列中的元素。队列只支
持两种操作:入队和出队。
如果你将两个元素加入队列,先加入的元素将在后加入的元素之前出队。因此,你可使用队
列来表示查找名单!这样,先加入的人将先出队并先被检查。
队列是一种先进先出(First In First Out,FIFO)的数据结构,而栈是一种后进先出(Last In
First Out,LIFO)的数据结构。
3.算法
3.1 选择排序
选择排序是一种灵巧的算法,但其速度不是很快,将数组中的元素选择出最大值然后依次放入新的数组中,不停循环这个操作,以得出最终顺序
需要的总时间为 O(n × n),即O(n 2 )。
3.2 递归
(1) 创建一个要查找的盒子堆。
(2) 从盒子堆取出一个盒子,在里面找。
(3) 如果找到的是盒子,就将其加入盒子堆中,以便以后再查找。
(4) 如果找到钥匙,则大功告成!
(5) 回到第二步。
OR
(1) 检查盒子中的每样东西。
(2) 如果是盒子,就回到第一步。
(3) 如果是钥匙,就大功告成!
编写递归函数时,必须告诉它何时停止递归。正因为如此,每个递归函数都有两部分:基线
条件(base case)和递归条件(recursive case)。递归条件指的是函数调用自己,而基线条件则
指的是函数不再调用自己,从而避免形成无限循环。
3.3 快速排序\分而治之(D&C)
使用D&C解决问题的过程包括两个步骤。
(1) 找出基线条件,这种条件必须尽可能简单。
(2) 不断将问题分解(或者说缩小规模),直到符合基线条件。
快速排序是一种常用的排序算法,比选择排序快得多。快速排序也使用了D&C。
下面来使用快速排序对数组进行排序。对排序算法来说,最简单的数
组什么样呢?还记得前一节的“提示”吗?就是根本不需要排序的数组。
因此,基线条件为数组为空或只包含一个元素。在这种情况下,只需
原样返回数组——根本就不用排序。
要使用D&C,因此需要将数组分解,直到满足基线条件。下面介绍快速排序的工
作原理。首先,从数组中选择一个元素,这个元素被称为基准值(pivot)。我们暂时将数组的第一个元素用作基准值。
接下来,找出比基准值小的元素以及比基准值大的元素。
这被称为分区(partitioning)。现在你有:
一个由所有小于基准值的数字组成的子数组;
基准值;
一个由所有大于基准值的数组组成的子数组。
如果子数组是有序的,就可以像下面这样合并得到一个有序的数组:左边的数组 + 基准值 +
右边的数组。在这里,就是[10, 15] + [33] + [],结果为有序数组[10, 15, 33]。
这个子数组都只有一个元素,而你知道如何对这些数组进行排序。现在你就知道如何对包含
三个元素的数组进行排序了,步骤如下。
(1) 选择基准值。
(2) 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。
(3) 对这两个子数组进行快速排序。
还有一种名为合并排序(merge sort)的排序算法,其运行时间为O(n log n),比选择排序快
得多!
快速排序的性能高度依赖于你选择的基准值。假设你总是将第一个元素用作基准值,且要处
理的数组是有序的。由于快速排序算法不检查输入数组是否有序,因此它依然尝试对其进行排序。
注意,数组并没有被分成两半,相反,其中一个子数组始终为空,这导致调用栈非常长。现
在假设你总是将中间的元素用作基准值,在这种情况下,调用栈如下。
调用栈短得多!因为你每次都将数组分成两半,所以不需要那么多递归调用。你很快就到达
了基线条件,因此调用栈短得多。
第一个示例展示的是最糟情况,而第二个示例展示的是最佳情况。在最糟情况下,栈长为
O(n),而在最佳情况下,栈长为O(log n)。
D&C将问题逐步分解。使用D&C处理列表时,基线条件很可能是空数组或只包含一个元
素的数组。
实现快速排序时,请随机地选择用作基准值的元素。快速排序的平均运行时间为O(n log n)。
大O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在。
比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时,O(log n)的速度比O(n)
快得多。
3.4 广度优先搜索是一种用于图的查找算法,可帮助回答两类问题。
第一类问题:从节点A出发,有前往节点B的路径吗?
第二类问题:从节点A出发,前往节点B的哪条路径最短?
l例子待续
3.5 狄克斯特拉算法
狄克斯特拉算法用于每条边都有关联数字的图,这些数字称为权重(weight)。
带权重的图称为加权图(weighted graph),不带权重的图称为非加权图(unweighted graph)。
广度优先搜索用于在非加权图中查找最短路径。
狄克斯特拉算法用于在加权图中查找最短路径。
仅当权重为正时狄克斯特拉算法才管用