一. 基础知识与数据结构
- 选择排序O(n^2) 快速排序O(nlogn)
- 对链表的操作所需的运行时间到底是多少呢?在这里,我们把链表中的数据量记成 n。访问数据时,我们需要从链表头部开始查找(线性查找),如果目标数据在链表最后 的话,需要的时间就是 O(n)。 另外,添加数据只需要更改两个指针的指向,所以耗费的时间与n 无关。如果已经到达了添加数据的位置,那么添加操作只需花费O(1) 的时间。删除数据同样也只需 O(1) 的时间。
- 想要保存数量固定的最新数据时通常会使用环形链表
“双向链表”不仅可以从前往后, 还可以从后往前遍历数据,十分方便。但是双向链表存在两个缺点:一是指针数的增加会导致存储空间需求增加;二是 添加和删除数据时需要改变更多指针的指向
- 数组:按顺序存储在内存的连续空间内,可以“随机访问”。对数组操作所花费的运行时间。假设数组中有n 个数据,由于访问数 据时使用的是随机访问(通过下标可计算出内存地址),所以需要的运行时间仅为恒定的 O(1)。但另一方面,想要向数组中添加新数据时,必须把目标位置后面的数据一个个移 开。所以,如果在数组头部添加数据,就需要 O(n) 的时间。删除操作同理
- 栈:入栈–push,出栈–pop。后进先出,称为Last In First Out 简称LIFO。在栈中,添加和删除数据的操作只 能在一端进行,访问数据也只能访问到顶端的数据。想要访问中间的数据时,就必须通 过出栈操作将目标数据移到栈顶才行
8. 队列:虽然与栈有些相似,但队列中添加和删除数据的操作分别是在两端进行的。像队列这种最先进去的数据最先被取来,即“先进先出”的结构,我们称为 First In First Out,简称 FIFO
9. 哈希表:在哈希表这种数据结构中,使用将在5-3 节讲解的“哈希函数”,可以使数据的查询效率得 到显著提升。 在哈希表中,我们可以利用哈希函数快速访问到数组中的目标数据。如果发生哈希 冲突,就使用链表进行存储。 这样一来,不管数据量为多少,我们都能够灵活应对。 如果数组的空间太小,使用哈希表的时候就容易发生冲突,线性查找的使用频率也 会更高;反过来,如果数组的空间太大,就会出现很多空箱子,造成内存的浪费。因此, 给数组设定合适的空间非常重要。
10. 堆:堆是一种图的树形结构,被用于实现“优先队列”(priority queues)。优先队列是一种数据结构,可以自由添加数据,但取出数据时要从最小值开始按顺序取出。在堆的树形结构中,各个顶点被称为“结点”(node),数据就存储在这些结点中。
(离散数学的Dijkstra算法)
- 二叉查找树:二叉查找树(又叫作二叉搜索树或二叉排序树)是一种数据结构,采用了图的树形结构,数据存储于二叉查找树的各个结点中。
二:排序
-
冒泡、插入、选择排序都是O(n^2)
-
堆排序:堆排序一开始需要将 n 个数据存进堆里,所需时间为 O(nlogn)。排序过程中,堆从 空堆的状态开始,逐渐被数据填满。由于堆的高度小于 log2n,所以插入 1 个数据所需要 的时间为 O(logn)。 每轮取出最大的数据并重构堆所需要的时间为O(logn)。由于总共有n 轮,所以重 构后排序的时间也是 O(nlogn)。因此,整体来看堆排序的时间复杂度为 O(nlogn)。 这样来看,堆排序的运行时间比之前讲到的冒泡排序、选择排序、插入排序的时间 O(n2) 都要短,但由于要使用堆这个相对复杂的数据结构,所以实现起来也较为困难。
-
归并排序:归并排序会把序列分成长度相同的两个子序列,当无法继续往下分时(也就是每个子 序列中只有一个数据时),就对子序列进行归并。归并指的是把两个排好序的子序列合并成一个 有序序列。该操作会一直重复执行,直到所有子序列都归并为一个整体为止。
算法分析:归并排序中,分割序列所花费的时间不算在运行时间内(可以当作序列本来就是分 割好的)。在合并两个已排好序的子序列时,只需重复比较首位数据的大小,然后移动较小的数据,因此只需花费和两个子序列的长度相应的运行时间。也就是说,完成一行归并所需的运行时间取决于这一行的数据量。 看一下上面的图便能得知,无论哪一行都是n个数据,所以每行的运行时间都为O(n)。 而将长度为n 的序列对半分割直到只有一个数据为止时,可以分成log2n 行,因此,总 共有 log2n 行。也就是说,总的运行时间为 O(nlogn),这与前面讲到的堆排序相同 -
快速排序:快速排序算法首先会在序列中随机选择一个基准值(pivot),然后将除了基准值以外的数分 为“比基准值小的数”和“比基准值大的数”这两个类别,再将其排列成以下形式。
[ 比基准值小的数 ] 基准值 [ 比基准值大的数 ]接着,对两个“[ ]”中的数据进行排序之后,整体的排序便完成了。对“[ ]”里面的数据 进行排序时同样也会使用快速排序
算法分析:快速排序是一种**“分治法”**。它将原本的问题分成两个子问题(比基准值小的数和 比基准值大的数),然后再分别解决这两个问题。子问题,也就是子序列完成排序后,再像一开始说明的那样,把他们合并成一个序列,那么对原始序列的排序也就完成了。 不过,解决子问题的时候会再次使用快速排序,甚至在这个快速排序里仍然要使用快速排序。只有在子问题里只剩一个数字的时候,排序才算完成。 像这样,在算法内部继续使用该算法的现象被称为“递归”。实际上前一节中讲到的归并排序也可看作是一种递归的分治法。
分割子序列时需要选择基准值,如果每次选择的基准值都能使得两个子序列的长度 为原本的一半,那么快速排序的运行时间和归并排序的一样,都为 O(nlogn)。(平均时间)
三. 查找
- 线性查找:O(n)
- 二分查找:它只能查找已经排好序的数据。二分查找利用已排好序的数组,每一次查找都可以将查找范围减半。查找范围内只剩一个数据时查找结束。数据量为n的数组,将其长度减半log2n 次后,其中便只剩一个数据了。也就是说, 在二分查找中重复执行“将目标数据和数组中间的数据进行比较后将查找范围减半”的 操作log2n 次后,就能找到目标数据(若没找到则可以得出数据不存在的结论),因此它 的时间复杂度为 O(logn)
四. 图的搜索
- 什么是图?比如离散数学的加权图、有向图等等+。
- 图的优势:假设图中有两个顶点s 和 t,而我们设计出了一种算法, 可以找到“从 s 到 t 的权重之和最小”的那条路径。 那么,这种算法就可以应用到这些问题上:寻找计算机网络中通信时间最短的路径,寻找路线图中耗时最短的路径,寻找路线图中最省乘车费的路径等 。
- 广度优先搜索:广度优先搜索是一种对图进行搜索的算法。假设我们一开始位于某个顶点(即起点),此时并不知道图的整体结构,而我们的目的是从起点开始顺着边搜索,直到到达指定顶点(即终点)。在此过程中每走到一个顶点,就会判断一次它是否为终点。广度优先搜索会优先从离起点近的顶点开始搜索。
候补顶点是用“先入先出”(FIFO)的方式来管理的,因此可以使用**“队列”**这个数据结构。 - 深度优先搜索:和广度优先搜索一样,都是对图进行搜索的算法,目的也都是从起点开始搜 索直到到达指定顶点(终点)。深度优先搜索会沿着一条路径不断往下搜索直到不能再继续为 止,然后再折返,开始搜索下一条候补路径
候补顶点是用“后入先出”(LIFO)的方式来管理的,因此可以使用**“栈”**这个 数据结构 - 贝尔曼 - 福特(Bellman-Ford)算法是一种在图中求解最短路径问题的算法。最短路径问 题就是在加权图指定了起点和终点的前提下,寻找从起点到终点的路径中权重总和最小的那条 路径。