mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-14 02:10:37 +08:00
build
This commit is contained in:
@@ -3,17 +3,17 @@ comments: true
|
||||
icon: material/timer-sand
|
||||
---
|
||||
|
||||
# 第 2 章 复杂度
|
||||
# 第 2 章 时空复杂度
|
||||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
{ width="600" }
|
||||
{ width="600" }
|
||||
|
||||
</div>
|
||||
|
||||
!!! abstract
|
||||
|
||||
复杂度犹如浩瀚的算法宇宙中的时空向导。
|
||||
复杂度分析犹如浩瀚的算法宇宙中的时空向导。
|
||||
|
||||
它带领我们在时间与空间这两个维度上深入探索,寻找更优雅的解决方案。
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ comments: true
|
||||
|
||||
/* 函数 */
|
||||
int function() {
|
||||
// do something...
|
||||
// 执行某些操作...
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ comments: true
|
||||
|
||||
/* 函数 */
|
||||
int func() {
|
||||
// do something...
|
||||
// 执行某些操作...
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ comments: true
|
||||
|
||||
def function() -> int:
|
||||
"""函数"""
|
||||
# do something...
|
||||
# 执行某些操作...
|
||||
return 0
|
||||
|
||||
def algorithm(n) -> int: # 输入数据
|
||||
@@ -116,7 +116,7 @@ comments: true
|
||||
|
||||
/* 函数 */
|
||||
func function() int {
|
||||
// do something...
|
||||
// 执行某些操作...
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ comments: true
|
||||
|
||||
/* 函数 */
|
||||
function constFunc() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ comments: true
|
||||
|
||||
/* 函数 */
|
||||
function constFunc(): number {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ comments: true
|
||||
```c title=""
|
||||
/* 函数 */
|
||||
int func() {
|
||||
// do something...
|
||||
// 执行某些操作...
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ comments: true
|
||||
|
||||
/* 函数 */
|
||||
int function() {
|
||||
// do something...
|
||||
// 执行某些操作...
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ comments: true
|
||||
|
||||
/* 函数 */
|
||||
func function() -> Int {
|
||||
// do something...
|
||||
// 执行某些操作...
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ comments: true
|
||||
|
||||
/* 函数 */
|
||||
int function() {
|
||||
// do something...
|
||||
// 执行某些操作...
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -441,7 +441,7 @@ comments: true
|
||||
|
||||
```java title=""
|
||||
int function() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
/* 循环 O(1) */
|
||||
@@ -461,7 +461,7 @@ comments: true
|
||||
|
||||
```cpp title=""
|
||||
int func() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
/* 循环 O(1) */
|
||||
@@ -481,7 +481,7 @@ comments: true
|
||||
|
||||
```python title=""
|
||||
def function() -> int:
|
||||
# do something
|
||||
# 执行某些操作
|
||||
return 0
|
||||
|
||||
def loop(n: int):
|
||||
@@ -499,7 +499,7 @@ comments: true
|
||||
|
||||
```go title=""
|
||||
func function() int {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -523,7 +523,7 @@ comments: true
|
||||
|
||||
```javascript title=""
|
||||
function constFunc() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
/* 循环 O(1) */
|
||||
@@ -543,7 +543,7 @@ comments: true
|
||||
|
||||
```typescript title=""
|
||||
function constFunc(): number {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
/* 循环 O(1) */
|
||||
@@ -563,7 +563,7 @@ comments: true
|
||||
|
||||
```c title=""
|
||||
int func() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
/* 循环 O(1) */
|
||||
@@ -583,7 +583,7 @@ comments: true
|
||||
|
||||
```csharp title=""
|
||||
int function() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
/* 循环 O(1) */
|
||||
@@ -604,7 +604,7 @@ comments: true
|
||||
```swift title=""
|
||||
@discardableResult
|
||||
func function() -> Int {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -634,7 +634,7 @@ comments: true
|
||||
|
||||
```dart title=""
|
||||
int function() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
/* 循环 O(1) */
|
||||
@@ -686,7 +686,7 @@ $$
|
||||
```java title="space_complexity.java"
|
||||
/* 函数 */
|
||||
int function() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -713,7 +713,7 @@ $$
|
||||
```cpp title="space_complexity.cpp"
|
||||
/* 函数 */
|
||||
int func() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -740,7 +740,7 @@ $$
|
||||
```python title="space_complexity.py"
|
||||
def function() -> int:
|
||||
"""函数"""
|
||||
# do something
|
||||
# 执行某些操作
|
||||
return 0
|
||||
|
||||
def constant(n: int):
|
||||
@@ -762,7 +762,7 @@ $$
|
||||
```go title="space_complexity.go"
|
||||
/* 函数 */
|
||||
func function() int {
|
||||
// do something...
|
||||
// 执行某些操作...
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -791,7 +791,7 @@ $$
|
||||
```javascript title="space_complexity.js"
|
||||
/* 函数 */
|
||||
function constFunc() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -818,7 +818,7 @@ $$
|
||||
```typescript title="space_complexity.ts"
|
||||
/* 函数 */
|
||||
function constFunc(): number {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -845,7 +845,7 @@ $$
|
||||
```c title="space_complexity.c"
|
||||
/* 函数 */
|
||||
int func() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -873,7 +873,7 @@ $$
|
||||
```csharp title="space_complexity.cs"
|
||||
/* 函数 */
|
||||
int function() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -901,7 +901,7 @@ $$
|
||||
/* 函数 */
|
||||
@discardableResult
|
||||
func function() -> Int {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -958,7 +958,7 @@ $$
|
||||
```dart title="space_complexity.dart"
|
||||
/* 函数 */
|
||||
int function() {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -985,7 +985,7 @@ $$
|
||||
```rust title="space_complexity.rs"
|
||||
/* 函数 */
|
||||
fn function() ->i32 {
|
||||
// do something
|
||||
// 执行某些操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1274,7 +1274,7 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
以下递归函数会同时存在 $n$ 个未返回的 `algorithm()` 函数,使用 $O(n)$ 大小的栈帧空间:
|
||||
以下函数的递归深度为 $n$ ,即同时存在 $n$ 个未返回的 `linear_recur()` 函数,使用 $O(n)$ 大小的栈帧空间:
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -1635,7 +1635,7 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
在以下递归函数中,同时存在 $n$ 个未返回的 `algorithm()` ,并且每个函数中都初始化了一个数组,长度分别为 $n, n-1, n-2, ..., 2, 1$ ,平均长度为 $\frac{n}{2}$ ,因此总体占用 $O(n^2)$ 空间。
|
||||
以下函数的递归深度为 $n$ ,在每个递归函数中都初始化了一个数组,长度分别为 $n, n-1, n-2, ..., 2, 1$ ,平均长度为 $n / 2$ ,因此总体占用 $O(n^2)$ 空间。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -1972,11 +1972,9 @@ $$
|
||||
|
||||
### 5. 对数阶 $O(\log n)$
|
||||
|
||||
对数阶常见于分治算法和数据类型转换等。
|
||||
对数阶常见于分治算法。例如归并排序,输入长度为 $n$ 的数组,每轮递归将数组从中点划分为两半,形成高度为 $\log n$ 的递归树,使用 $O(\log n)$ 栈帧空间。
|
||||
|
||||
例如归并排序算法,输入长度为 $n$ 的数组,每轮递归将数组从中点划分为两半,形成高度为 $\log n$ 的递归树,使用 $O(\log n)$ 栈帧空间。
|
||||
|
||||
再例如将数字转化为字符串,输入任意正整数 $n$ ,它的位数为 $\log_{10} n + 1$ ,即对应字符串长度为 $\log_{10} n + 1$ ,因此空间复杂度为 $O(\log_{10} n + 1) = O(\log n)$ 。
|
||||
再例如将数字转化为字符串,输入一个正整数 $n$ ,它的位数为 $\log_{10} n + 1$ ,即对应字符串长度为 $\log_{10} n + 1$ ,因此空间复杂度为 $O(\log_{10} n + 1) = O(\log n)$ 。
|
||||
|
||||
## 2.3.4 权衡时间与空间
|
||||
|
||||
|
||||
@@ -1606,11 +1606,7 @@ $$
|
||||
|
||||
<p align="center"> 图:常数阶、线性阶和平方阶的时间复杂度 </p>
|
||||
|
||||
以冒泡排序为例,外层循环执行 $n - 1$ 次,内层循环执行 $n-1, n-2, \cdots, 2, 1$ 次,平均为 $\frac{n}{2}$ 次,因此时间复杂度为 $O(n^2)$ :
|
||||
|
||||
$$
|
||||
O((n - 1) \frac{n}{2}) = O(n^2)
|
||||
$$
|
||||
以冒泡排序为例,外层循环执行 $n - 1$ 次,内层循环执行 $n-1, n-2, \dots, 2, 1$ 次,平均为 $n / 2$ 次,因此时间复杂度为 $O((n - 1) n / 2) = O(n^2)$ 。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -2557,7 +2553,17 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是理想的时间复杂度,仅次于常数阶。
|
||||
对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是仅次于常数阶的理想的时间复杂度。
|
||||
|
||||
!!! tip
|
||||
|
||||
准确来说,“一分为 $m$”对应的时间复杂度是 $O(\log_m n)$ 。而通过对数换底公式,我们可以得到具有不同底数的、相等的时间复杂度:
|
||||
|
||||
$$
|
||||
O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n)
|
||||
$$
|
||||
|
||||
因此我们通常会省略底数 $m$ ,将对数阶直接记为 $O(\log n)$ 。
|
||||
|
||||
### 6. 线性对数阶 $O(n \log n)$
|
||||
|
||||
@@ -2756,7 +2762,7 @@ $$
|
||||
阶乘阶对应数学上的“全排列”问题。给定 $n$ 个互不重复的元素,求其所有可能的排列方案,方案数量为:
|
||||
|
||||
$$
|
||||
n! = n \times (n - 1) \times (n - 2) \times \cdots \times 2 \times 1
|
||||
n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1
|
||||
$$
|
||||
|
||||
阶乘通常使用递归实现。例如在以下代码中,第一层分裂出 $n$ 个,第二层分裂出 $n - 1$ 个,以此类推,直至第 $n$ 层时停止分裂:
|
||||
@@ -2953,7 +2959,7 @@ $$
|
||||
|
||||
<p align="center"> 图:阶乘阶的时间复杂度 </p>
|
||||
|
||||
请注意,因为 $n! > 2^n$ ,所以阶乘阶比指数阶增长得更快,在 $n$ 较大时也是不可接受的。
|
||||
请注意,因为当 $n \geq 4$ 时恒有 $n! > 2^n$ ,所以阶乘阶比指数阶增长得更快,在 $n$ 较大时也是不可接受的。
|
||||
|
||||
## 2.2.5 最差、最佳、平均时间复杂度
|
||||
|
||||
@@ -3318,7 +3324,7 @@ $$
|
||||
|
||||
从上述示例可以看出,最差或最佳时间复杂度只出现于“特殊的数据分布”,这些情况的出现概率可能很小,并不能真实地反映算法运行效率。相比之下,**平均时间复杂度可以体现算法在随机输入数据下的运行效率**,用 $\Theta$ 记号来表示。
|
||||
|
||||
对于部分算法,我们可以简单地推算出随机数据分布下的平均情况。比如上述示例,由于输入数组是被打乱的,因此元素 $1$ 出现在任意索引的概率都是相等的,那么算法的平均循环次数就是数组长度的一半 $\frac{n}{2}$ ,平均时间复杂度为 $\Theta(\frac{n}{2}) = \Theta(n)$ 。
|
||||
对于部分算法,我们可以简单地推算出随机数据分布下的平均情况。比如上述示例,由于输入数组是被打乱的,因此元素 $1$ 出现在任意索引的概率都是相等的,那么算法的平均循环次数就是数组长度的一半 $n / 2$ ,平均时间复杂度为 $\Theta(n / 2) = \Theta(n)$ 。
|
||||
|
||||
但对于较为复杂的算法,计算平均时间复杂度往往是比较困难的,因为很难分析出在数据分布下的整体数学期望。在这种情况下,我们通常使用最差时间复杂度作为算法效率的评判标准。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user