【游戏程序开发必知】10大基础实用算法及其讲解
算法一:快速排序算法
快速排序(Quick Sort)是由东尼·霍尔(Tony Hoare)所发展的一种排序算法。在平均情况下,排序 $n$ 个项目需要 $O(n \log n)$ 次比较;在最坏情况下则需要 $O(n^2)$ 次比较,但这种最坏情况并不常见。实际上,快速排序通常明显比其他 $O(n \log n)$ 算法更快,因为它的内部循环可以在大部分架构上高效地实现。
快速排序使用分治法(Divide and Conquer)策略,将一个序列(list)划分为两个子序列(sub-lists)。
算法步骤
- 从数列中挑选一个元素,称为 “基准”(pivot)。
- 重新排列数列,将所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准后面(相同的数可以放在任一边)。分区操作完成后,基准元素就处于数列的中间位置。
- 递归地对小于基准值元素的子数列和大于基准值元素的子数列进行排序。
递归的终止条件是数列的大小为 0 或 1,此时数列已经是有序的。虽然会一直递归下去,但该算法总会终止,因为在每次迭代中,至少会有一个元素被放置到其最终位置。
算法二:堆排序算法
堆排序(Heapsort)是利用堆这种数据结构设计的一种排序算法。堆是一种近似完全二叉树的结构,同时满足堆的性质:子结点的键值或索引总是小于(或者大于)它的父节点。
堆排序的平均时间复杂度为 $O(n \log n)$。
算法步骤
- 创建一个堆 $H[0..n - 1]$。
- 交换堆首(最大值)和堆尾元素。
- 将堆的尺寸缩小 1,并调用
shift_down(0)函数,目的是将新的数组顶端数据调整到相应位置。 - 重复步骤 2,直到堆的尺寸为 1。
算法三:归并排序
归并排序(Merge Sort,台湾译作:合并排序)是基于归并操作的一种有效排序算法,是分治法(Divide and Conquer)的典型应用。
算法步骤
- 申请一个空间,其大小为两个已排序序列长度之和,该空间用于存放合并后的序列。
- 设置两个指针,初始位置分别为两个已排序序列的起始位置。
- 比较两个指针所指向的元素,选择较小的元素放入合并空间,并将指针移动到下一位置。
- 重复步骤 3,直到某一指针到达序列末尾。
- 将另一个序列剩余的所有元素直接复制到合并序列的末尾。
算法四:二分查找算法
二分查找算法是一种在有序数组中查找特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素恰好是要查找的元素,则搜索结束;如果特定元素大于或小于中间元素,则在数组中大于或小于中间元素的那一半中继续查找,且同样从中间元素开始比较。如果在某一步骤数组为空,则表示未找到目标元素。这种搜索算法每次比较都使搜索范围缩小一半,时间复杂度为 $O(\log n)$。
算法五:BFPRT(线性查找算法)
BFPRT 算法解决的问题非常经典,即从 $n$ 个元素的序列中选出第 $k$ 大(第 $k$ 小)的元素。通过巧妙的分析,BFPRT 可以保证在最坏情况下仍为线性时间复杂度 $O(n)$。该算法的思想与快速排序相似,但为了使算法在最坏情况下仍能达到 $O(n)$ 的时间复杂度,五位算法作者做了精妙的处理。
算法步骤
- 将 $n$ 个元素每 5 个一组,分成 $\lceil n/5 \rceil$ 组。
- 对每一组使用任意排序方法(如插入排序),取出其中位数。
- 递归调用
selection算法,查找上一步中所有中位数的中位数,设为 $x$;若中位数个数为偶数,则选取中间较小的一个。 - 用 $x$ 分割数组,设小于等于 $x$ 的元素个数为 $k$,则大于 $x$ 的元素个数为 $n - k$。
- 若 $i == k$,返回 $x$;若 $i < k$,在小于等于 $x$ 的元素中递归查找第 $i$ 小的元素;若 $i > k$,在大于 $x$ 的元素中递归查找第 $i - k$ 小的元素。
终止条件:当 $n = 1$ 时,返回的即是第 $i$ 小元素。
算法六:DFS(深度优先搜索)
深度优先搜索算法(Depth-First Search,DFS)是一种搜索算法。它沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点 $v$ 的所有边都被探寻过,搜索将回溯到发现节点 $v$ 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,直到所有节点都被访问为止。DFS 属于盲目搜索。
深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以生成目标图的相应拓扑排序表,利用拓扑排序表可以方便地解决很多相关的图论问题,如最大路径问题等。一般使用栈数据结构来辅助实现 DFS 算法。
深度优先遍历图算法步骤
- 访问顶点 $v$。
- 依次从 $v$ 的未被访问的邻接点出发,对图进行深度优先遍历,直至图中和 $v$ 有路径相通的顶点都被访问。
- 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。
实例说明
上述描述可能比较抽象,下面举个实例:DFS 在访问图中某一起始顶点 $v$ 后,从 $v$ 出发,访问它的任一邻接顶点 $w_1$;再从 $w_1$ 出发,访问与 $w_1$ 邻接但还未访问过的顶点 $w_2$;然后再从 $w_2$ 出发,进行类似的访问,如此进行下去,直至到达所有的邻接顶点都被访问过的顶点 $u$ 为止。
接着,退回一步,退到前一次刚访问过的顶点,查看是否还有其他未被访问的邻接顶点。如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。
算法七:BFS(广度优先搜索)
广度优先搜索算法(Breadth-First Search,BFS)是一种图形搜索算法。简单来说,BFS 从根节点开始,沿着树(图)的宽度遍历树(图)的节点。如果所有节点均被访问,则算法终止。BFS 同样属于盲目搜索。一般使用队列数据结构来辅助实现 BFS 算法。
算法步骤
- 首先将根节点放入队列中。
- 从队列中取出第一个节点,并检查它是否为目标。
- 如果找到目标,则结束搜索并返回结果。
- 否则,将它所有尚未检查过的直接子节点加入队列中。
- 若队列为空,表示整张图都检查过了,即图中没有要搜索的目标,结束搜索并返回 “找不到目标”。
- 重复步骤 2。
算法八:Dijkstra 算法
戴克斯特拉算法(Dijkstra’s algorithm)由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)提出。该算法使用广度优先搜索解决非负权有向图的单源最短路径问题,最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。
算法的输入包含一个有权重的有向图 $G$,以及 $G$ 中的一个源顶点 $S$。用 $V$ 表示 $G$ 中所有顶点的集合,每一个图中的边是由两个顶点形成的有序元素对,$(u, v)$ 表示从顶点 $u$ 到 $v$ 有路径相连。用 $E$ 表示 $G$ 中所有边的集合,边的权重由权重函数 $w: E \to [0, \infty]$ 定义,因此 $w(u, v)$ 就是从顶点 $u$ 到顶点 $v$ 的非负权重。边的权重可以看作两个顶点之间的距离,任两点间路径的权重是该路径上所有边的权重总和。已知 $V$ 中有顶点 $s$ 及 $t$,Dijkstra 算法可以找到 $s$ 到 $t$ 的最低权重路径(例如,最短路径),也可以在一个图中找到从一个顶点 $s$ 到任何其他顶点的最短路径。对于不含负权的有向图,Dijkstra 算法是目前已知的最快的单源最短路径算法。
算法步骤
- 初始时,令 $S = {V_0}$,$T = {其余顶点}$,$T$ 中顶点对应的距离值:
- 若存在 $(V_0, V_i)$ 弧,$d(V_0, V_i)$ 为弧上的权值。
- 若不存在 $(V_0, V_i)$ 弧,$d(V_0, V_i)$ 为 $\infty$。
- 从 $T$ 中选取一个距离值最小且不在 $S$ 中的顶点 $W$,将其加入 $S$。
- 修改其余 $T$ 中顶点的距离值:若加入 $W$ 作为中间顶点后,从 $V_0$ 到 $V_i$ 的距离值缩短,则修改此距离值。
- 重复上述步骤 2、3,直到 $S$ 中包含所有顶点,即 $W = V_i$ 为止。
算法九:动态规划算法
动态规划(Dynamic Programming)是一种在数学、计算机科学和经济学中使用的方法,通过将原问题分解为相对简单的子问题来求解复杂问题。动态规划常常适用于具有重叠子问题和最优子结构性质的问题,其所需时间往往远少于朴素解法。
动态规划背后的基本思想很简单:要解决一个给定问题,需要先解决其不同部分(即子问题),再合并子问题的解以得出原问题的解。通常许多子问题非常相似,为此动态规划法试图仅解决每个子问题一次,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。
关于动态规划最经典的问题当属背包问题。
算法步骤
- 最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
- 子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只需在表格中简单地查看结果,从而获得较高的效率。
算法十:朴素贝叶斯分类算法
朴素贝叶斯分类算法是一种基于贝叶斯定理的简单概率分类算法。贝叶斯分类的基础是概率推理,即在各种条件的存在不确定,仅知其出现概率的情况下,如何完成推理和决策任务。概率推理是与确定性推理相对应的。而朴素贝叶斯分类器基于独立假设,即假设样本每个特征与其他特征都不相关。
朴素贝叶斯分类器依靠精确的自然概率模型,在有监督学习的样本集中能获得非常好的分类效果。在许多实际应用中,朴素贝叶斯模型参数估计使用最大似然估计方法,换言之,朴素贝叶斯模型能工作并没有用到贝叶斯概率或者任何贝叶斯模型。
尽管带有这些朴素思想和过于简单化的假设,但朴素贝叶斯分类器在很多复杂的现实情形中仍能取得相当好的效果。