This commit is contained in:
krahets
2025-12-31 19:37:45 +08:00
parent 29ec0c699d
commit 3c9d5689c4
279 changed files with 40895 additions and 16087 deletions

View File

@@ -116,12 +116,6 @@ comments: true
[class]{}-[func]{for_loop}
```
=== "Zig"
```zig title="iteration.zig"
[class]{}-[func]{forLoop}
```
以下の図はこの合計関数を表しています。
![Flowchart of the sum function](iteration_and_recursion.assets/iteration.png){ class="animation-figure" }
@@ -242,12 +236,6 @@ comments: true
[class]{}-[func]{while_loop}
```
=== "Zig"
```zig title="iteration.zig"
[class]{}-[func]{whileLoop}
```
**`while`ループは`for`ループよりも柔軟性を提供します**。特に、条件変数のカスタム初期化と各ステップでの変更が可能です。
例えば、以下のコードでは、条件変数$i$が各ラウンドで2回更新されますが、これは`for`ループでは実装が不便です。
@@ -364,12 +352,6 @@ comments: true
[class]{}-[func]{while_loop_ii}
```
=== "Zig"
```zig title="iteration.zig"
[class]{}-[func]{whileLoopII}
```
全体的に、**`for`ループはより簡潔で、`while`ループはより柔軟です**。どちらも反復構造を実装できます。どちらを使用するかは、問題の具体的な要件に基づいて決定する必要があります。
### 3.   ネストしたループ
@@ -484,12 +466,6 @@ comments: true
[class]{}-[func]{nested_for_loop}
```
=== "Zig"
```zig title="iteration.zig"
[class]{}-[func]{nestedForLoop}
```
以下の図はこのネストしたループを表しています。
![Flowchart of the nested loop](iteration_and_recursion.assets/nested_iteration.png){ class="animation-figure" }
@@ -619,12 +595,6 @@ comments: true
[class]{}-[func]{recur}
```
=== "Zig"
```zig title="recursion.zig"
[class]{}-[func]{recur}
```
以下の図はこの関数の再帰プロセスを示しています。
![Recursive process of the sum function](iteration_and_recursion.assets/recursion_sum.png){ class="animation-figure" }
@@ -763,12 +733,6 @@ comments: true
[class]{}-[func]{tail_recur}
```
=== "Zig"
```zig title="recursion.zig"
[class]{}-[func]{tailRecur}
```
末尾再帰の実行プロセスは以下の図に示されています。通常の再帰と末尾再帰を比較すると、合計操作のポイントが異なります。
- **通常の再帰**: 合計操作は「返却」フェーズで発生し、各レイヤーが返った後にもう一度合計が必要です。
@@ -901,12 +865,6 @@ comments: true
[class]{}-[func]{fib}
```
=== "Zig"
```zig title="recursion.zig"
[class]{}-[func]{fib}
```
上記のコードを観察すると、それ自体の中で2つの関数を再帰的に呼び出していることがわかります。**つまり、1回の呼び出しで2つの分岐呼び出しが生成されます**。以下の図に示されているように、この継続的な再帰呼び出しは最終的に深さ$n$の<u>再帰木</u>を作成します。
![Fibonacci sequence recursion tree](iteration_and_recursion.assets/recursion_tree.png){ class="animation-figure" }
@@ -1075,12 +1033,6 @@ comments: true
[class]{}-[func]{for_loop_recur}
```
=== "Zig"
```zig title="recursion.zig"
[class]{}-[func]{forLoopRecur}
```
上記のコードを観察すると、再帰が反復に変換されたとき、コードはより複雑になります。反復と再帰はしばしば相互に変換できますが、2つの理由でそうすることが常に推奨されるわけではありません
- 変換されたコードは理解がより困難になり、読みにくくなる可能性があります。

View File

@@ -322,12 +322,6 @@ comments: true
```
=== "Zig"
```zig title=""
```
## 2.4.2 &nbsp; 計算方法
空間計算量を計算する方法は時間計算量とほぼ同様で、統計対象を「操作数」から「使用空間のサイズ」に変更するだけです。
@@ -474,12 +468,6 @@ comments: true
```
=== "Zig"
```zig title=""
```
**再帰関数では、スタックフレーム空間を考慮に入れる必要があります**。以下のコードを考えてみましょう:
=== "Python"
@@ -718,12 +706,6 @@ comments: true
```
=== "Zig"
```zig title=""
```
`loop()`関数と`recur()`関数の時間計算量は両方とも$O(n)$ですが、それらの空間計算量は異なります。
- `loop()`関数はループ内で`function()`を$n$回呼び出し、各反復の`function()`は返ってそのスタックフレーム空間を解放するため、空間計算量は$O(1)$のままです。
@@ -906,14 +888,6 @@ $$
[class]{}-[func]{constant}
```
=== "Zig"
```zig title="space_complexity.zig"
[class]{}-[func]{function}
[class]{}-[func]{constant}
```
### 2. &nbsp; 線形オーダー $O(n)$ {data-toc-label="2. &nbsp; 線形オーダー"}
線形オーダーは配列、連結リスト、スタック、キューなどで一般的で、要素数は$n$に比例します:
@@ -1033,12 +1007,6 @@ $$
[class]{}-[func]{linear}
```
=== "Zig"
```zig title="space_complexity.zig"
[class]{}-[func]{linear}
```
下図に示されているように、この関数の再帰深度は$n$で、$n$個の未返却の`linear_recur()`関数インスタンスがあり、$O(n)$サイズのスタックフレーム空間を使用します:
=== "Python"
@@ -1136,12 +1104,6 @@ $$
[class]{}-[func]{linear_recur}
```
=== "Zig"
```zig title="space_complexity.zig"
[class]{}-[func]{linearRecur}
```
![Recursive function generating linear order space complexity](space_complexity.assets/space_complexity_recursive_linear.png){ class="animation-figure" }
<p align="center"> 図 2-17 &nbsp; Recursive function generating linear order space complexity </p>
@@ -1255,12 +1217,6 @@ $$
[class]{}-[func]{quadratic}
```
=== "Zig"
```zig title="space_complexity.zig"
[class]{}-[func]{quadratic}
```
下図に示されているように、この関数の再帰深度は$n$で、各再帰呼び出しで長さ$n$、$n-1$、$\dots$、$2$、$1$の配列が初期化され、平均$n/2$となり、全体として$O(n^2)$の空間を占有します:
=== "Python"
@@ -1362,12 +1318,6 @@ $$
[class]{}-[func]{quadratic_recur}
```
=== "Zig"
```zig title="space_complexity.zig"
[class]{}-[func]{quadraticRecur}
```
![Recursive function generating quadratic order space complexity](space_complexity.assets/space_complexity_recursive_quadratic.png){ class="animation-figure" }
<p align="center"> 図 2-18 &nbsp; Recursive function generating quadratic order space complexity </p>
@@ -1477,12 +1427,6 @@ $$
[class]{}-[func]{build_tree}
```
=== "Zig"
```zig title="space_complexity.zig"
[class]{}-[func]{buildTree}
```
![Full binary tree generating exponential order space complexity](space_complexity.assets/space_complexity_exponential.png){ class="animation-figure" }
<p align="center"> 図 2-19 &nbsp; Full binary tree generating exponential order space complexity </p>

View File

@@ -181,21 +181,6 @@ comments: true
```
=== "Zig"
```zig title=""
// 特定の操作プラットフォーム下で
fn algorithm(n: usize) void {
var a: i32 = 2; // 1 ns
a += 1; // 1 ns
a *= 2; // 10 ns
// n回ループ
for (0..n) |_| { // 1 ns
std.debug.print("{}\n", .{0}); // 5 ns
}
}
```
上記の方法を使用すると、アルゴリズムの実行時間は$(6n + 12)$ nsとして計算できます
$$
@@ -445,29 +430,6 @@ $$
```
=== "Zig"
```zig title=""
// アルゴリズムAの時間計算量定数オーダー
fn algorithm_A(n: usize) void {
_ = n;
std.debug.print("{}\n", .{0});
}
// アルゴリズムBの時間計算量線形オーダー
fn algorithm_B(n: i32) void {
for (0..n) |_| {
std.debug.print("{}\n", .{0});
}
}
// アルゴリズムCの時間計算量定数オーダー
fn algorithm_C(n: i32) void {
_ = n;
for (0..1000000) |_| {
std.debug.print("{}\n", .{0});
}
}
```
下図はこれら3つのアルゴリズムの時間計算量を示しています。
- アルゴリズム`A`には1つの印刷操作のみがあり、その実行時間は$n$とともに増加しません。その時間計算量は「定数オーダー」と考えられます。
@@ -647,20 +609,6 @@ $$
```
=== "Zig"
```zig title=""
fn algorithm(n: usize) void {
var a: i32 = 1; // +1
a += 1; // +1
a *= 2; // +1
// n回ループ
for (0..n) |_| { // +1 (毎回i++が実行される)
std.debug.print("{}\n", .{0}); // +1
}
}
```
アルゴリズムの操作数を入力サイズ$n$の関数として表す関数を$T(n)$とすると、以下の例を考えてみましょう:
$$
@@ -910,27 +858,6 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし
```
=== "Zig"
```zig title=""
fn algorithm(n: usize) void {
var a: i32 = 1; // +0 (技法1)
a = a + @as(i32, @intCast(n)); // +0 (技法1)
// +n (技法2)
for(0..(5 * n + 1)) |_| {
std.debug.print("{}\n", .{0});
}
// +n*n (技法3)
for(0..(2 * n)) |_| {
for(0..(n + 1)) |_| {
std.debug.print("{}\n", .{0});
}
}
}
```
以下の式は、簡略化前後のカウント結果を示しており、どちらも$O(n^2)$の時間計算量に導きます:
$$
@@ -1078,12 +1005,6 @@ $$
[class]{}-[func]{constant}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{constant}
```
### 2. &nbsp; 線形オーダー $O(n)$ {data-toc-label="2. &nbsp; 線形オーダー"}
線形オーダーは、操作数が入力データサイズ$n$と線形に増加することを示します。線形オーダーは一般的に単一ループ構造で現れます:
@@ -1183,12 +1104,6 @@ $$
[class]{}-[func]{linear}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{linear}
```
配列の走査や連結リストの走査などの操作は時間計算量が$O(n)$で、$n$は配列またはリストの長さです:
=== "Python"
@@ -1291,12 +1206,6 @@ $$
[class]{}-[func]{array_traversal}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{arrayTraversal}
```
**入力データサイズ$n$は入力データの種類に基づいて決定する必要があります**。例えば、最初の例では、$n$は入力データサイズを表し、2番目の例では、配列の長さ$n$がデータサイズです。
### 3. &nbsp; 二次オーダー $O(n^2)$ {data-toc-label="3. &nbsp; 二次オーダー"}
@@ -1408,12 +1317,6 @@ $$
[class]{}-[func]{quadratic}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{quadratic}
```
下図は定数オーダー、線形オーダー、二次オーダーの時間計算量を比較しています。
![Constant, linear, and quadratic order time complexities](time_complexity.assets/time_complexity_constant_linear_quadratic.png){ class="animation-figure" }
@@ -1547,12 +1450,6 @@ $$
[class]{}-[func]{bubble_sort}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{bubbleSort}
```
### 4. &nbsp; 指数オーダー $O(2^n)$ {data-toc-label="4. &nbsp; 指数オーダー"}
生物学的「細胞分裂」は指数オーダー増加の典型例です1つの細胞から始まり、1回の分裂後に2つ、2回の分裂後に4つとなり、$n$回の分裂後に$2^n$個の細胞になります。
@@ -1671,12 +1568,6 @@ $$
[class]{}-[func]{exponential}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{exponential}
```
![Exponential order time complexity](time_complexity.assets/time_complexity_exponential.png){ class="animation-figure" }
<p align="center"> 図 2-11 &nbsp; Exponential order time complexity </p>
@@ -1775,12 +1666,6 @@ $$
[class]{}-[func]{exp_recur}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{expRecur}
```
指数オーダーの増加は極めて急速で、全数探索法(ブルートフォース、バックトラッキングなど)でよく見られます。大規模問題では、指数オーダーは受け入れられず、しばしば動的プログラミングや貪欲アルゴリズムが解決策として必要になります。
### 5. &nbsp; 対数オーダー $O(\log n)$ {data-toc-label="5. &nbsp; 対数オーダー"}
@@ -1889,12 +1774,6 @@ $$
[class]{}-[func]{logarithmic}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{logarithmic}
```
![Logarithmic order time complexity](time_complexity.assets/time_complexity_logarithmic.png){ class="animation-figure" }
<p align="center"> 図 2-12 &nbsp; Logarithmic order time complexity </p>
@@ -1993,12 +1872,6 @@ $$
[class]{}-[func]{log_recur}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{logRecur}
```
対数オーダーは分割統治戦略に基づくアルゴリズムの典型で、「多くに分割」と「複雑な問題を単純化」するアプローチを体現しています。増加が遅く、定数オーダーの次に最も理想的な時間計算量です。
!!! tip "$O(\log n)$の底は何ですか?"
@@ -2118,12 +1991,6 @@ $$
[class]{}-[func]{linear_log_recur}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{linearLogRecur}
```
下図は線形対数オーダーがどのように生成されるかを示しています。二分木の各レベルには$n$個の操作があり、木には$\log_2 n + 1$レベルがあり、時間計算量は$O(n \log n)$になります。
![Linear-logarithmic order time complexity](time_complexity.assets/time_complexity_logarithmic_linear.png){ class="animation-figure" }
@@ -2248,12 +2115,6 @@ $$
[class]{}-[func]{factorial_recur}
```
=== "Zig"
```zig title="time_complexity.zig"
[class]{}-[func]{factorialRecur}
```
![Factorial order time complexity](time_complexity.assets/time_complexity_factorial.png){ class="animation-figure" }
<p align="center"> 図 2-14 &nbsp; Factorial order time complexity </p>
@@ -2431,14 +2292,6 @@ $$
[class]{}-[func]{find_one}
```
=== "Zig"
```zig title="worst_best_time_complexity.zig"
[class]{}-[func]{randomNumbers}
[class]{}-[func]{findOne}
```
最良ケース時間計算量は実際にはほとんど使用されないことに注意してください。通常は非常に低い確率でのみ達成可能で、誤解を招く可能性があるからです。**最悪ケース時間計算量はより実用的で、効率の安全値を提供し**、アルゴリズムを自信を持って使用できるようにします。
上記の例から、最悪ケースと最良ケースの時間計算量は両方とも「特殊なデータ分布」下でのみ発生し、発生確率が小さく、アルゴリズムの実行効率を正確に反映しない可能性があることが明らかです。対照的に、**平均時間計算量はランダム入力データ下でのアルゴリズムの効率を反映でき**、$\Theta$記法で表されます。