1. 算法复杂度分析:从“感觉”到“科学”的必经之路
很多刚接触算法的同学,心里总有个疑问:我怎么知道我的算法写得好不好?最常见的回答是:“跑一下,看谁快呗。” 这当然没错,但问题来了,你在你的电脑上跑得快,换台老旧的电脑可能就慢如蜗牛;处理100条数据飞快,处理100万条数据可能就直接卡死。这时候,我们就需要一套更科学、更本质的评价体系——这就是算法复杂度分析。
你可以把它想象成给算法做“体检报告”。我们不只看它现在跑100米用了多少秒(实际运行时间),更要看它的“身体素质”和“成长潜力”。复杂度分析就是分析算法的时间消耗和空间消耗如何随着输入数据规模(通常用 n 表示)的增长而增长。这才是算法好坏的核心指标,它剥离了具体机器性能、编程语言差异等外部因素,直指算法设计本身的优劣。
在考试或者面试里,复杂度分析题几乎是必考题,就像山东大学那道选择题,给你两个函数 f(n) 和 g(n),让你判断它们之间的渐进关系。这里的关键是掌握几个符号:O(大O,上界)、Ω(大Ω,下界)、Θ(大Θ,紧确界)。我刚开始也老记混,后来用一个土办法:把 O(n²) 想象成“最坏不会超过 n² 这个量级”,把 Ω(n) 想象成“最好也不会比 n 这个量级更快”,把 Θ(n log n) 想象成“它跑的速度就是稳稳的 n log n 这个级别”。做这类题,别慌,常用的方法就是取极限,比较 f(n)/g(n) 当 n 趋向于无穷大时的极限值。
比如,判断 f(n) = n² / log n 和 g(n) = n (log n)² 的关系。我们计算 lim (n→∞) [f(n) / g(n)] = lim (n→∞) [(n² / log n) / (n (log n)²)] = lim (n→∞) [n / (log n)³]。这个极限显然是趋向于无穷大的,这意味着 f(n) 的增长速度最终远快于 g(n)。所以,说 f(n) = Ω(g(n))(f(n) 是 g(n) 的下界)就是错误的,因为实际上是 f(n) 更大,应该是 f(n) = ω(g(n))(非紧确下界,或者说 f(n) 的阶更高)。
再说说排序算法。快排(Quick Sort)的最优时间复杂度是 O(n log n),这发生在每次划分都能将数组几乎对半分的时候。而它的平均时间复杂度也是 O(n log n),这意味着在随机数据下,它的表现非常出色。但它的最坏情况是 O(n²),也就是数组已经有序或逆序,且选取的pivot总是最值的时候。所以,在实际工程中,我们常会采用随机选择pivot或者“三数取中”法来极力避免最坏情况的发生。理解这些,你就能明白为什么在实际中快排如此受欢迎,以及什么时候需要警惕它的性能陷阱。
2. 动态规划思想:Floyd-Warshall算法的智慧
动态规划(Dynamic Programming, DP)是算法设计里的一座大山,也是面试中的重灾区。很多人觉得DP难,是因为它不像直接写循环那样直观。但在我看来,DP的核心思想就两点:记忆化搜索(或表格递推) 和最优子结构。山东大学考到的Floyd-Warshall算法,就是动态规划思想在图论中的一个绝佳应用。
Floyd-Warshall算法解决的是所有顶点对之间的最短路径问题。简单说,就是给你一个图(可能包含负权边,但不能有负权环),它能算出任意两个点 i 和 j 之间的最短距离。最朴素的想法是,以每个点为起点,跑一遍Dijkstra算法(如果没负权边)。但如果有负权边呢?Dijkstra就失效了。这时Flo

4479

被折叠的 条评论
为什么被折叠?



