优化算法

时间:2020-04-15 19:33:09   收藏:0   阅读:57

优化算法

多数优化方法的性能改善效果是线性的,但是使用更高效的算法替换低效算法可以使性能呈现指数增长。设计高效算法是许多计算机科学教科书和博士学术论文的主题。计算机科学家之所以研究重要的算法和数据结构,是因为它们是展示如何优化代码的典型示例。

算法的时间开销

算法的时间开销是一个抽象的数学函数,它描述了随着输入数据规模的增加,算法的时间开销会如何增长。时间开销通常用大O标记表示,例如\(O(f(n))\),其中\(n\)是某个会显著影响输入数据规模的因素,\(f(n)\)描述的是一个算法对规模为\(n\)的输入数据执行了多少次显著的操作。通常,\(f(n)\)被简化为仅表示增长最快的因素,因为对于很大的\(n\)来说,这个因素决定了\(f(n)\)的值。

最优情况、平均情况和最差情况的时间开销

通常的大O标记假设算法对任意输入数据集的运行时间是相同的。不过,有些算法对输入数据的特性非常敏感,例如,它们在按照某种顺序排序的输入数据上的运行速度,可能比在其他规模相同但顺序不同的输入数据上的运行速度上要快。当考虑在有严格的性能需求的代码中使用哪种算法时,非常重要的一点是必须知道该算法是否有最差情况。

有些算法在最优情况下同样也具有最优时间开销,例如,对那些已经排序完成的输入数据集进行排序时的时间开销会较小。当输入数据集具有某些可以利用的特性时,选择一种在最优情况下具有最佳性能的算法可以减少程序的运行时间。

摊销时间开销

摊销时间开销表示在大量输入数据上的平均时间开销。例如,向堆中插入一个元素的时间复杂度是\(O(log_2n)\),那么如果每次插入一个元素,构建整个堆的时间就是\(O(nlog_2n)\)。不过构建堆的最高效方法的时间开销是\(O(n)\),这意味着该方法插入每个元素的摊销时间复杂度是\(O(1)\)。但是最高效的算法并不会每次只插入一个元素。它会使用分治法(divide-and-conquer algorithm)将所有数据插入到依次增大的子堆中。

最显著的摊销时间开销,发生在当某些独立的操作很快而其他操作很慢时。例如,将一个字符添加到std::string中的摊销时间开销是一个常量,但这其中包含了一次对内存管理器的调用所占用的部分时间。如果这个字符串很短,那么可能几乎每次在添加字符的时候都需要调用内存管理器。只有当程序再添加了数千个或是数百万个字符后,摊销时间开销才会变小。

其他开销

有时候,通过保存中间结果可以提高算法的速度。因此这种算法不仅有时间开销,还有额外的存储开销。例如,我们所熟知的遍历二叉树的递归算法的时间开销是线性的,但是在递归过程中还会发生额外的\(log_2n\)的栈空间存储开销。需要大量存储空间开销的算法可能不适用于内存容量很小的运行环境。

另外还有一些算法是在进行并行计算时会更快,但是需要购买相应数量的处理器来获取理论上的速度提升。在普通的计算机上,处理器的数量很少,也是固定的。因此,对于那些需要多于\(log_2n\)个处理器的算法来说,使用普通计算机不合适。这些算法可能适用于为特殊用途构建的硬件或是图形处理器上。

优化查找和排序的工具箱

高效查找算法

查找算法的时间开销

当n很小时,所有算法的时间开销都一样

高效排序算法

排序算法的时间开销

排序算法 最好情况 平均情况 最差情况 空间需求 最好/最差情况的注意点
插入排序 \(n\) \(n^2\) \(n^2\) 1 最好情况出现在数据集已经排序完成时
快速排序 \(nlog_2n\) \(nlog_2n\) \(n^2\) \(log_2n\) 最差情况出现在数据集已经排序完成时
或是支点元素的原生选择(第一个/最后一个)
归并排序 \(nlog_2n\) \(nlog_2n\) \(nlog_2n\) 1
树形排序 \(nlog_2n\) \(nlog_2n\) \(nlog_2n\) \(n\)
堆排序 \(nlog_2n\) \(nlog_2n\) \(nlog_2n\) 1
Timsort \(n\) \(nlog_2n\) \(nlog_2n\) \(n\) 最好情况出现在当前数据集已经排序完成时
内省排序 \(nlog_2n\) \(nlog_2n\) \(nlog_2n\) 1

Timsort是一种相对较新的混合型排序算法,现在已经成为Python语言的标准排序算法了。

内省排序(introsort)算法是快速排序和堆排序的混合形式。内省排序首先以快速排序算法开始进行排序,但是当输入数据集导致快速排序的递归深度太深时,会切换为堆排序。自C++11开始,内省排序已经成为了std::sort()的优先实现。

Flash Sort对于抽取自某种概率分布的数据,它的性能达到了\(O(n)\)。与基数排序类似,都是基于概率分布的包分为将数据排序至桶中。Flash Sort的一个简单的适用场景是当数据元素均匀分布时。

优化模式

预计算

可以在程序早期,例如设计、编译、链接时,通过在热点代码前执行计算来讲计算从热点部分中移除。预计算仅当被计算的值不依赖于上下文时才适用。

延迟计算

通过在真正需要执行计算时才执行计算,可以将计算从某些代码路径中移除。

批量处理

每次对多个元素一起进行计算,而不是一次只对一个元素进行计算。

缓存

通过保存和复用昂贵计算的结果来减少计算量,而不是重复进行计算。

特化

通过移除未使用的共性来减少计算量。

提高处理量

通过一次处理一大组数据来减少循环处理的开销。

提示

通过在代码中加入可能会改善性能的提示来减少计算量。

优化期待路径

以期待频率从高到低的顺序对输入数据或是运行时发生的事件进行测试。例如,条件控制语句中,若有多个else if条件,发生几率最大的情况应写在前面。

散列法

计算可变长度字符串等大型数据结构的压缩数值映射(散列值)。在进行比较时,用散列值代替数据结构可以提高性能。

双重检查

通过先进行一项开销不大的检查,然后只在必要时才进行另外一项开销昂贵的检查来减少计算量。

原文:https://www.cnblogs.com/fr-ruiyang/p/12707424.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!