docs: add Japanese translate documents (#1812)

* docs: add Japanese documents (`ja/docs`)

* docs: add Japanese documents (`ja/codes`)

* docs: add Japanese documents

* Remove pythontutor blocks in ja/

* Add an empty at the end of each markdown file.

* Add the missing figures (use the English version temporarily).

* Add index.md for Japanese version.

* Add index.html for Japanese version.

* Add missing index.assets

* Fix backtracking_algorithm.md for Japanese version.

* Add avatar_eltociear.jpg. Fix image links on the Japanese landing page.

* Add the Japanese banner.

---------

Co-authored-by: krahets <krahets@163.com>
This commit is contained in:
Ikko Eltociear Ashimine
2025-10-17 06:04:43 +09:00
committed by GitHub
parent 2487a27036
commit 954c45864b
886 changed files with 33569 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

@@ -0,0 +1,221 @@
# 配列
<u>配列</u>は線形データ構造で、同じような項目が並んでいるようなもので、コンピュータのメモリ内の連続した空間に一緒に格納されます。これは整理された格納を維持するシーケンスのようなものです。この並びの各項目には、<u>インデックス</u>として知られる独自の「位置」があります。以下の図を参照して、配列の動作を観察し、これらの重要な用語を理解してください。
![配列の定義と格納方法](array.assets/array_definition.png)
## 配列の一般的な操作
### 配列の初期化
配列は必要に応じて2つの方法で初期化できます:初期値なしまたは指定された初期値付きです。初期値が指定されていない場合、ほとんどのプログラミング言語は配列要素を$0$に設定します:
=== "Python"
```python title="array.py"
# 配列を初期化
arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ]
nums: list[int] = [1, 3, 2, 5, 4]
```
=== "C++"
```cpp title="array.cpp"
/* 配列を初期化 */
// スタックに格納
int arr[5];
int nums[5] = { 1, 3, 2, 5, 4 };
// ヒープに格納(手動でのメモリ解放が必要)
int* arr1 = new int[5];
int* nums1 = new int[5] { 1, 3, 2, 5, 4 };
```
=== "Java"
```java title="array.java"
/* 配列を初期化 */
int[] arr = new int[5]; // { 0, 0, 0, 0, 0 }
int[] nums = { 1, 3, 2, 5, 4 };
```
=== "C#"
```csharp title="array.cs"
/* 配列を初期化 */
int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ]
int[] nums = [1, 3, 2, 5, 4];
```
=== "Go"
```go title="array.go"
/* 配列を初期化 */
var arr [5]int
// Goでは、長さを指定([5]int)すると配列を示し、指定しない([]int)とスライスを示します。
// Goの配列はコンパイル時に固定長を持つよう設計されているため、長さの指定には定数のみ使用できます。
// extend()メソッドの実装の便宜上、ここではSliceを配列として扱います。
nums := []int{1, 3, 2, 5, 4}
```
=== "Swift"
```swift title="array.swift"
/* 配列を初期化 */
let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
let nums = [1, 3, 2, 5, 4]
```
=== "JS"
```javascript title="array.js"
/* 配列を初期化 */
var arr = new Array(5).fill(0);
var nums = [1, 3, 2, 5, 4];
```
=== "TS"
```typescript title="array.ts"
/* 配列を初期化 */
let arr: number[] = new Array(5).fill(0);
let nums: number[] = [1, 3, 2, 5, 4];
```
=== "Dart"
```dart title="array.dart"
/* 配列を初期化 */
List<int> arr = List.filled(5, 0); // [0, 0, 0, 0, 0]
List<int> nums = [1, 3, 2, 5, 4];
```
=== "Rust"
```rust title="array.rs"
/* 配列を初期化 */
let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0]
let slice: &[i32] = &[0; 5];
// Rustでは、長さを指定([i32; 5])すると配列を示し、指定しない(&[i32])とスライスを示します。
// Rustの配列はコンパイル時に固定長を持つよう設計されているため、長さの指定には定数のみ使用できます。
// 一般的にRustでは動的配列としてVectorが使用されます。
// extend()メソッドの実装の便宜上、ここではベクターを配列として扱います。
let nums: Vec<i32> = vec![1, 3, 2, 5, 4];
```
=== "C"
```c title="array.c"
/* 配列を初期化 */
int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 }
int nums[5] = { 1, 3, 2, 5, 4 };
```
=== "Kotlin"
```kotlin title="array.kt"
```
=== "Zig"
```zig title="array.zig"
// 配列を初期化
var arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 }
var nums = [_]i32{ 1, 3, 2, 5, 4 };
```
### 要素へのアクセス
配列内の要素は連続したメモリ空間に格納されるため、各要素のメモリアドレスを計算することが簡単になります。以下の図に示されている公式は、配列のメモリアドレス(特に、最初の要素のアドレス)と要素のインデックスを利用して、要素のメモリアドレスを決定するのに役立ちます。この計算により、目的の要素への直接アクセスが合理化されます。
![配列要素のメモリアドレス計算](array.assets/array_memory_location_calculation.png)
上の図で観察されるように、配列のインデックスは慣例的に$0$から始まります。これは直感に反するように見えるかもしれません。数を数えるのは通常$1$から始まるためですが、アドレス計算公式内では、**インデックスは本質的にメモリアドレスからのオフセット**です。最初の要素のアドレスでは、このオフセットは$0$で、そのインデックスが$0$であることを検証しています。
配列内の要素へのアクセスは非常に効率的で、$O(1)$時間で任意の要素にランダムアクセスできます。
```src
[file]{array}-[class]{}-[func]{random_access}
```
### 要素の挿入
配列要素はメモリ内で密に詰まっており、それらの間に追加データを収容するための空間はありません。以下の図に示すように、配列の中央に要素を挿入するには、後続のすべての要素を1つずつ後ろにシフトして、新しい要素のための空間を作る必要があります。
![配列要素挿入の例](array.assets/array_insert_element.png)
配列の長さが固定されているため、要素を挿入すると必然的に配列の最後の要素が失われることに注意することが重要です。この問題を解決する方法は「リスト」の章で探求されます。
```src
[file]{array}-[class]{}-[func]{insert}
```
### 要素の削除
同様に、以下の図に示すように、インデックス$i$の要素を削除するには、インデックス$i$に続くすべての要素を1つずつ前に移動する必要があります。
![配列要素削除の例](array.assets/array_remove_element.png)
削除後、元の最後の要素は「意味がない」ものになるため、特定の修正は必要ないことに注意してください。
```src
[file]{array}-[class]{}-[func]{remove}
```
要約すると、配列の挿入と削除操作には以下の欠点があります:
- **高い時間計算量**:配列の挿入と削除の両方の平均時間計算量は$O(n)$で、ここで$n$は配列の長さです。
- **要素の損失**:配列の長さが固定されているため、挿入時に配列の容量を超える要素は失われます。
- **メモリの無駄**:より長い配列を初期化して前部分のみを利用すると、挿入時に「意味のない」末尾要素が生じ、メモリ空間の無駄につながります。
### 配列の走査
ほとんどのプログラミング言語では、インデックスを使用するか、各要素を直接反復することで配列を走査できます:
```src
[file]{array}-[class]{}-[func]{traverse}
```
### 要素の検索
配列内の特定の要素を見つけることは、配列を反復し、各要素をチェックして目的の値と一致するかどうかを決定することを含みます。
配列は線形データ構造であるため、この操作は一般的に「線形探索」と呼ばれます。
```src
[file]{array}-[class]{}-[func]{find}
```
### 配列の拡張
複雑なシステム環境では、安全な容量拡張のために配列の後にメモリ空間の可用性を確保することが困難になります。その結果、ほとんどのプログラミング言語では、**配列の長さは不変**です。
配列を拡張するには、より大きな配列を作成し、元の配列から要素をコピーする必要があります。この操作の時間計算量は$O(n)$で、大きな配列では時間がかかる可能性があります。コードは以下の通りです:
```src
[file]{array}-[class]{}-[func]{extend}
```
## 配列の利点と制限
配列は連続したメモリ空間に格納され、同じ型の要素で構成されます。このアプローチは、システムがデータ構造操作の効率を最適化するために活用できる実質的な事前情報を提供します。
- **高い空間効率**:配列はデータのための連続したメモリブロックを割り当て、追加の構造的オーバーヘッドの必要性を排除します。
- **ランダムアクセスのサポート**:配列は任意の要素への$O(1)$時間アクセスを可能にします。
- **キャッシュ局所性**:配列要素にアクセスするとき、コンピュータはそれらを読み込むだけでなく、周囲のデータもキャッシュし、高速キャッシュを利用して後続の操作速度を向上させます。
しかし、連続空間格納は諸刃の剣で、以下の制限があります:
- **挿入と削除の効率が低い**:配列に多くの要素が蓄積されると、要素の挿入や削除には大量の要素をシフトする必要があります。
- **固定長**:配列の長さは初期化後に固定されます。配列を拡張するには、すべてのデータを新しい配列にコピーする必要があり、大きなコストがかかります。
- **空間の無駄**:割り当てられた配列サイズが必要以上に大きい場合、余分な空間が無駄になります。
## 配列の典型的な応用
配列は基本的で広く使用されるデータ構造です。様々なアルゴリズムで頻繁に応用され、複雑なデータ構造の実装に役立ちます。
- **ランダムアクセス**:配列はランダムサンプリングが必要なときのデータ格納に理想的です。インデックスに基づいてランダムシーケンスを生成することで、効率的にランダムサンプリングを実現できます。
- **ソートと検索**:配列はソートと検索アルゴリズムで最も一般的に使用されるデータ構造です。クイックソート、マージソート、二分探索などの技術は主に配列で動作します。
- **ルックアップテーブル**:配列は迅速な要素や関係の取得のための効率的なルックアップテーブルとして機能します。例えば、文字をASCIIコードにマッピングすることは、ASCIIコード値をインデックスとして使用し、対応する要素を配列に格納することで簡単になります。
- **機械学習**:ニューラルネットワークの領域では、配列はベクトル、行列、テンソルを含む重要な線形代数演算の実行において重要な役割を果たします。配列はニューラルネットワークプログラミングにおいて主要かつ最も広範囲に使用されるデータ構造として機能します。
- **データ構造の実装**:配列は、スタック、キュー、ハッシュ表、ヒープ、グラフなど、様々なデータ構造を実装するための構成要素として機能します。例えば、グラフの隣接行列表現は本質的に二次元配列です。
@@ -0,0 +1,9 @@
# 配列と連結リスト
![配列と連結リスト](../assets/covers/chapter_array_and_linkedlist.jpg)
!!! abstract
データ構造の世界は頑丈なレンガの壁に似ています。
配列では、レンガがぴったりと整列し、それぞれが次のものと継ぎ目なく隣り合って、統一された形成を作っている姿を想像してください。一方、連結リストでは、これらのレンガが自由に散らばり、それらの間を優雅に編み込む蔦に抱かれています。
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@@ -0,0 +1,686 @@
# 連結リスト
メモリ空間は、すべてのプログラム間で共有されるリソースです。複雑なシステム環境では、使用可能なメモリがメモリ空間全体に分散している可能性があります。配列に割り当てられるメモリは連続している必要があることを理解していますが、非常に大きな配列の場合、十分な大きさの連続メモリ空間を見つけるのは困難な場合があります。ここで、連結リストの柔軟な利点が明らかになります。
<u>連結リスト</u>は線形データ構造であり、各要素はノードオブジェクトで、ノードは「参照」を通じて相互接続されています。これらの参照は後続ノードのメモリアドレスを保持し、1つのノードから次のノードへのナビゲーションを可能にします。
連結リストの設計では、ノードを連続するメモリアドレスを必要とせずに、メモリ位置全体に分散配置することができます。
![連結リストの定義と格納方法](linked_list.assets/linkedlist_definition.png)
上図に示すように、連結リストの基本的な構成要素は<u>ノード</u>オブジェクトです。各ノードは2つの主要なコンポーネントで構成されています:ノードの「値」と次のノードへの「参照」です。
- 連結リストの最初のノードは「ヘッドノード」、最後のノードは「テールノード」です。
- テールノードは「null」を指し、Javaでは`null`、C++では`nullptr`、Pythonでは`None`として指定されます。
- C、C++、Go、Rustなどのポインタをサポートする言語では、この「参照」は通常「ポインタ」として実装されます。
以下のコードが示すように、連結リストの`ListNode`は値を保持するだけでなく、追加の参照(またはポインタ)も維持する必要があります。したがって、**連結リストは同じ量のデータを格納する場合、配列よりも多くのメモリ空間を占有します**。
=== "Python"
```python title=""
class ListNode:
"""連結リストノードクラス"""
def __init__(self, val: int):
self.val: int = val # ノード値
self.next: ListNode | None = None # 次のノードへの参照
```
=== "C++"
```cpp title=""
/* 連結リストノード構造体 */
struct ListNode {
int val; // ノード値
ListNode *next; // 次のノードへのポインタ
ListNode(int x) : val(x), next(nullptr) {} // コンストラクタ
};
```
=== "Java"
```java title=""
/* 連結リストノードクラス */
class ListNode {
int val; // ノード値
ListNode next; // 次のノードへの参照
ListNode(int x) { val = x; } // コンストラクタ
}
```
=== "C#"
```csharp title=""
/* 連結リストノードクラス */
class ListNode(int x) { // コンストラクタ
int val = x; // ノード値
ListNode? next; // 次のノードへの参照
}
```
=== "Go"
```go title=""
/* 連結リストノード構造体 */
type ListNode struct {
Val int // ノード値
Next *ListNode // 次のノードへのポインタ
}
// NewListNode コンストラクタ、新しい連結リストを作成
func NewListNode(val int) *ListNode {
return &ListNode{
Val: val,
Next: nil,
}
}
```
=== "Swift"
```swift title=""
/* 連結リストノードクラス */
class ListNode {
var val: Int // ノード値
var next: ListNode? // 次のノードへの参照
init(x: Int) { // コンストラクタ
val = x
}
}
```
=== "JS"
```javascript title=""
/* 連結リストノードクラス */
class ListNode {
constructor(val, next) {
this.val = (val === undefined ? 0 : val); // ノード値
this.next = (next === undefined ? null : next); // 次のノードへの参照
}
}
```
=== "TS"
```typescript title=""
/* 連結リストノードクラス */
class ListNode {
val: number;
next: ListNode | null;
constructor(val?: number, next?: ListNode | null) {
this.val = val === undefined ? 0 : val; // ノード値
this.next = next === undefined ? null : next; // 次のノードへの参照
}
}
```
=== "Dart"
```dart title=""
/* 連結リストノードクラス */
class ListNode {
int val; // ノード値
ListNode? next; // 次のノードへの参照
ListNode(this.val, [this.next]); // コンストラクタ
}
```
=== "Rust"
```rust title=""
use std::rc::Rc;
use std::cell::RefCell;
/* 連結リストノードクラス */
#[derive(Debug)]
struct ListNode {
val: i32, // ノード値
next: Option<Rc<RefCell<ListNode>>>, // 次のノードへのポインタ
}
```
=== "C"
```c title=""
/* 連結リストノード構造体 */
typedef struct ListNode {
int val; // ノード値
struct ListNode *next; // 次のノードへのポインタ
} ListNode;
/* コンストラクタ */
ListNode *newListNode(int val) {
ListNode *node;
node = (ListNode *) malloc(sizeof(ListNode));
node->val = val;
node->next = NULL;
return node;
}
```
=== "Kotlin"
```kotlin title=""
```
=== "Zig"
```zig title=""
// 連結リストノードクラス
pub fn ListNode(comptime T: type) type {
return struct {
const Self = @This();
val: T = 0, // ノード値
next: ?*Self = null, // 次のノードへのポインタ
// コンストラクタ
pub fn init(self: *Self, x: i32) void {
self.val = x;
self.next = null;
}
};
}
```
## 連結リストの一般的な操作
### 連結リストの初期化
連結リストの構築は2段階のプロセスです:まず各ノードオブジェクトを初期化し、次にノード間の参照リンクを形成します。初期化後、ヘッドノードから`next`参照をたどってすべてのノードを順次巡回できます。
=== "Python"
```python title="linked_list.py"
# 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4
# 各ノードを初期化
n0 = ListNode(1)
n1 = ListNode(3)
n2 = ListNode(2)
n3 = ListNode(5)
n4 = ListNode(4)
# ノード間の参照を構築
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
```
=== "C++"
```cpp title="linked_list.cpp"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
ListNode* n0 = new ListNode(1);
ListNode* n1 = new ListNode(3);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(5);
ListNode* n4 = new ListNode(4);
// ノード間の参照を構築
n0->next = n1;
n1->next = n2;
n2->next = n3;
n3->next = n4;
```
=== "Java"
```java title="linked_list.java"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
ListNode n0 = new ListNode(1);
ListNode n1 = new ListNode(3);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(5);
ListNode n4 = new ListNode(4);
// ノード間の参照を構築
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```
=== "C#"
```csharp title="linked_list.cs"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
ListNode n0 = new(1);
ListNode n1 = new(3);
ListNode n2 = new(2);
ListNode n3 = new(5);
ListNode n4 = new(4);
// ノード間の参照を構築
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```
=== "Go"
```go title="linked_list.go"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
n0 := NewListNode(1)
n1 := NewListNode(3)
n2 := NewListNode(2)
n3 := NewListNode(5)
n4 := NewListNode(4)
// ノード間の参照を構築
n0.Next = n1
n1.Next = n2
n2.Next = n3
n3.Next = n4
```
=== "Swift"
```swift title="linked_list.swift"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
let n0 = ListNode(x: 1)
let n1 = ListNode(x: 3)
let n2 = ListNode(x: 2)
let n3 = ListNode(x: 5)
let n4 = ListNode(x: 4)
// ノード間の参照を構築
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
```
=== "JS"
```javascript title="linked_list.js"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
const n0 = new ListNode(1);
const n1 = new ListNode(3);
const n2 = new ListNode(2);
const n3 = new ListNode(5);
const n4 = new ListNode(4);
// ノード間の参照を構築
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```
=== "TS"
```typescript title="linked_list.ts"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
const n0 = new ListNode(1);
const n1 = new ListNode(3);
const n2 = new ListNode(2);
const n3 = new ListNode(5);
const n4 = new ListNode(4);
// ノード間の参照を構築
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```
=== "Dart"
```dart title="linked_list.dart"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
ListNode n0 = ListNode(1);
ListNode n1 = ListNode(3);
ListNode n2 = ListNode(2);
ListNode n3 = ListNode(5);
ListNode n4 = ListNode(4);
// ノード間の参照を構築
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```
=== "Rust"
```rust title="linked_list.rs"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None }));
let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None }));
let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None }));
let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None }));
let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None }));
// ノード間の参照を構築
n0.borrow_mut().next = Some(n1.clone());
n1.borrow_mut().next = Some(n2.clone());
n2.borrow_mut().next = Some(n3.clone());
n3.borrow_mut().next = Some(n4.clone());
```
=== "C"
```c title="linked_list.c"
/* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */
// 各ノードを初期化
ListNode* n0 = newListNode(1);
ListNode* n1 = newListNode(3);
ListNode* n2 = newListNode(2);
ListNode* n3 = newListNode(5);
ListNode* n4 = newListNode(4);
// ノード間の参照を構築
n0->next = n1;
n1->next = n2;
n2->next = n3;
n3->next = n4;
```
=== "Kotlin"
```kotlin title="linked_list.kt"
```
=== "Zig"
```zig title="linked_list.zig"
// 連結リストを初期化
// 各ノードを初期化
var n0 = inc.ListNode(i32){.val = 1};
var n1 = inc.ListNode(i32){.val = 3};
var n2 = inc.ListNode(i32){.val = 2};
var n3 = inc.ListNode(i32){.val = 5};
var n4 = inc.ListNode(i32){.val = 4};
// ノード間の参照を構築
n0.next = &n1;
n1.next = &n2;
n2.next = &n3;
n3.next = &n4;
```
配列全体は1つの変数です。例えば、配列`nums`には`nums[0]`、`nums[1]`などの要素が含まれますが、連結リストは複数の異なるノードオブジェクトで構成されています。**通常、連結リストはそのヘッドノードで参照されます**。例えば、前のコードスニペットの連結リストは`n0`として参照されます。
### ノードの挿入
連結リストにノードを挿入するのは非常に簡単です。下図に示すように、隣接する2つのノード`n0`と`n1`の間に新しいノード`P`を挿入することを目指すとします。**これは2つのノード参照(ポインタ)を変更するだけで実現でき**、時間計算量は$O(1)$です。
比較すると、配列に要素を挿入する時間計算量は$O(n)$であり、大量のデータを扱う場合には効率が悪くなります。
![連結リストノード挿入の例](linked_list.assets/linkedlist_insert_node.png)
```src
[file]{linked_list}-[class]{}-[func]{insert}
```
### ノードの削除
下図に示すように、連結リストからノードを削除することも非常に簡単で、**1つのノードの参照(ポインタ)を変更するだけです**。
重要な点は、ノード`P`が削除された後も`n1`を指し続けていることですが、連結リストの巡回中にはアクセスできなくなることです。これは事実上、`P`が連結リストの一部ではなくなったことを意味します。
![連結リストノードの削除](linked_list.assets/linkedlist_remove_node.png)
```src
[file]{linked_list}-[class]{}-[func]{remove}
```
### ノードへのアクセス
**連結リストでのノードへのアクセスは効率が悪いです**。前述したように、配列の任意の要素には$O(1)$時間でアクセスできます。対照的に、連結リストでは、プログラムはヘッドノードから開始して目的のノードが見つかるまで順次ノードを巡回する必要があります。つまり、連結リストの$i$番目のノードにアクセスするには、プログラムは$i - 1$個のノードを反復処理する必要があり、時間計算量は$O(n)$になります。
```src
[file]{linked_list}-[class]{}-[func]{access}
```
### ノードの検索
連結リストを巡回して、値が`target`に一致するノードを見つけ、連結リスト内でのそのノードのインデックスを出力します。この手順も線形検索の例です。対応するコードは以下のとおりです:
```src
[file]{linked_list}-[class]{}-[func]{find}
```
## 配列 vs. 連結リスト
下表は配列と連結リストの特性をまとめ、様々な操作における効率も比較しています。それぞれが対照的な格納戦略を使用するため、それぞれの特性と操作効率は明確に対比されています。
<p align="center"> 表 <id> &nbsp; 配列と連結リストの効率比較 </p>
| | 配列 | 連結リスト |
| ------------------ | ------------------------------------------------ | ----------------------- |
| 格納方式 | 連続メモリ空間 | 分散メモリ空間 |
| 容量拡張 | 固定長 | 柔軟な拡張 |
| メモリ効率 | 要素あたりのメモリ少、潜在的な空間の無駄 | 要素あたりのメモリ多 |
| 要素へのアクセス | $O(1)$ | $O(n)$ |
| 要素の追加 | $O(n)$ | $O(1)$ |
| 要素の削除 | $O(n)$ | $O(1)$ |
## 連結リストの一般的な種類
下図に示すように、連結リストには3つの一般的な種類があります。
- **単方向連結リスト**:これは前述した標準的な連結リストです。単方向連結リストのノードには値と次のノードへの参照が含まれます。最初のノードはヘッドノードと呼ばれ、null(`None`)を指す最後のノードはテールノードです。
- **循環連結リスト**:これは単方向連結リストのテールノードがヘッドノードを指してループを作ることで形成されます。循環連結リストでは、任意のノードがヘッドノードとして機能できます。
- **双方向連結リスト**:単方向連結リストとは対照的に、双方向連結リストは2つの方向で参照を維持します。各ノードには後続者(次のノード)と前任者(前のノード)の両方への参照(ポインタ)が含まれます。双方向連結リストはどちらの方向にも巡回できるより多くの柔軟性を提供しますが、より多くのメモリ空間も消費します。
=== "Python"
```python title=""
class ListNode:
"""双方向連結リストノードクラス"""
def __init__(self, val: int):
self.val: int = val # ノード値
self.next: ListNode | None = None # 後続ノードへの参照
self.prev: ListNode | None = None # 前任ノードへの参照
```
=== "C++"
```cpp title=""
/* 双方向連結リストノード構造体 */
struct ListNode {
int val; // ノード値
ListNode *next; // 後続ノードへのポインタ
ListNode *prev; // 前任ノードへのポインタ
ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // コンストラクタ
};
```
=== "Java"
```java title=""
/* 双方向連結リストノードクラス */
class ListNode {
int val; // ノード値
ListNode next; // 次のノードへの参照
ListNode prev; // 前任ノードへの参照
ListNode(int x) { val = x; } // コンストラクタ
}
```
=== "C#"
```csharp title=""
/* 双方向連結リストノードクラス */
class ListNode(int x) { // コンストラクタ
int val = x; // ノード値
ListNode next; // 次のノードへの参照
ListNode prev; // 前任ノードへの参照
}
```
=== "Go"
```go title=""
/* 双方向連結リストノード構造体 */
type DoublyListNode struct {
Val int // ノード値
Next *DoublyListNode // 後続ノードへのポインタ
Prev *DoublyListNode // 前任ノードへのポインタ
}
// NewDoublyListNode 初期化
func NewDoublyListNode(val int) *DoublyListNode {
return &DoublyListNode{
Val: val,
Next: nil,
Prev: nil,
}
}
```
=== "Swift"
```swift title=""
/* 双方向連結リストノードクラス */
class ListNode {
var val: Int // ノード値
var next: ListNode? // 次のノードへの参照
var prev: ListNode? // 前任ノードへの参照
init(x: Int) { // コンストラクタ
val = x
}
}
```
=== "JS"
```javascript title=""
/* 双方向連結リストノードクラス */
class ListNode {
constructor(val, next, prev) {
this.val = val === undefined ? 0 : val; // ノード値
this.next = next === undefined ? null : next; // 後続ノードへの参照
this.prev = prev === undefined ? null : prev; // 前任ノードへの参照
}
}
```
=== "TS"
```typescript title=""
/* 双方向連結リストノードクラス */
class ListNode {
val: number;
next: ListNode | null;
prev: ListNode | null;
constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {
this.val = val === undefined ? 0 : val; // ノード値
this.next = next === undefined ? null : next; // 後続ノードへの参照
this.prev = prev === undefined ? null : prev; // 前任ノードへの参照
}
}
```
=== "Dart"
```dart title=""
/* 双方向連結リストノードクラス */
class ListNode {
int val; // ノード値
ListNode next; // 次のノードへの参照
ListNode prev; // 前任ノードへの参照
ListNode(this.val, [this.next, this.prev]); // コンストラクタ
}
```
=== "Rust"
```rust title=""
use std::rc::Rc;
use std::cell::RefCell;
/* 双方向連結リストノード型 */
#[derive(Debug)]
struct ListNode {
val: i32, // ノード値
next: Option<Rc<RefCell<ListNode>>>, // 後続ノードへのポインタ
prev: Option<Rc<RefCell<ListNode>>>, // 前任ノードへのポインタ
}
/* コンストラクタ */
impl ListNode {
fn new(val: i32) -> Self {
ListNode {
val,
next: None,
prev: None,
}
}
}
```
=== "C"
```c title=""
/* 双方向連結リストノード構造体 */
typedef struct ListNode {
int val; // ノード値
struct ListNode *next; // 後続ノードへのポインタ
struct ListNode *prev; // 前任ノードへのポインタ
} ListNode;
/* コンストラクタ */
ListNode *newListNode(int val) {
ListNode *node, *next;
node = (ListNode *) malloc(sizeof(ListNode));
node->val = val;
node->next = NULL;
node->prev = NULL;
return node;
}
```
=== "Kotlin"
```kotlin title=""
```
=== "Zig"
```zig title=""
// 双方向連結リストノードクラス
pub fn ListNode(comptime T: type) type {
return struct {
const Self = @This();
val: T = 0, // ノード値
next: ?*Self = null, // 後続ノードへのポインタ
prev: ?*Self = null, // 前任ノードへのポインタ
// コンストラクタ
pub fn init(self: *Self, x: i32) void {
self.val = x;
self.next = null;
self.prev = null;
}
};
}
```
![連結リストの一般的な種類](linked_list.assets/linkedlist_common_types.png)
## 連結リストの典型的な応用
単方向連結リストは、スタック、キュー、ハッシュ表、グラフの実装によく使用されます。
- **スタックとキュー**:単方向連結リストで、挿入と削除が同じ端で行われる場合、スタック(後入先出)のように動作します。逆に、挿入が一方の端で、削除がもう一方の端で行われる場合、キュー(先入先出)のように機能します。
- **ハッシュ表**:連結リストは、ハッシュ衝突を解決する人気の方法である連鎖法で使用されます。ここでは、すべての衝突した要素が連結リストにグループ化されます。
- **グラフ**:グラフ表現の標準的な方法である隣接リストは、各グラフ頂点を連結リストに関連付けます。このリストには、対応する頂点に接続された頂点を表す要素が含まれます。
双方向連結リストは、前後の要素への高速アクセスが必要なシナリオに最適です。
- **高度なデータ構造**:赤黒木やB木などの構造では、ノードの親へのアクセスが重要です。これは各ノードに親ノードへの参照を組み込むことで実現され、双方向連結リストに似ています。
- **ブラウザ履歴**:Webブラウザでは、双方向連結リストにより、ユーザーが前進または後退ボタンをクリックしたときの訪問ページの履歴ナビゲーションが容易になります。
- **LRUアルゴリズム**:双方向連結リストは、最近最少使用(LRU)キャッシュ削除アルゴリズムに適しており、最近最少使用データの迅速な識別と、高速なノード追加・削除を可能にします。
循環連結リストは、オペレーティングシステムでのリソーススケジューリングなど、周期的な操作が必要なアプリケーションに最適です。
- **ラウンドロビンスケジューリングアルゴリズム**:オペレーティングシステムでは、ラウンドロビンスケジューリングアルゴリズムは一般的なCPUスケジューリング方法であり、プロセスのグループを循環する必要があります。各プロセスにはタイムスライスが割り当てられ、期限切れになるとCPUは次のプロセスに回転します。この循環操作は循環連結リストを使用して効率的に実現でき、すべてのプロセス間で公平かつ時分割システムを可能にします。
- **データバッファ**:循環連結リストは、オーディオやビデオプレーヤーなどのデータバッファでも使用され、データストリームが複数のバッファブロックに分割され、シームレスな再生のために循環方式で配置されます。
@@ -0,0 +1,906 @@
# リスト
<u>リスト</u>は、要素へのアクセス、変更、追加、削除、走査などの操作をサポートする、順序付けられた要素のコレクションを表す抽象的なデータ構造の概念であり、ユーザーが容量制限を考慮する必要がありません。リストは連結リストまたは配列に基づいて実装できます。
- 連結リストは本質的にリストとして機能し、要素の追加、削除、検索、変更の操作をサポートし、サイズを動的に調整する柔軟性があります。
- 配列もこれらの操作をサポートしますが、長さが不変であるため、長さ制限のあるリストと考えることができます。
配列を使用してリストを実装する場合、**長さの不変性によりリストの実用性が低下します**。これは、事前に格納するデータ量を予測することが困難な場合が多く、適切なリスト長を選択することが困難であるためです。長さが小さすぎると要件を満たさない可能性があり、大きすぎるとメモリ空間を無駄にする可能性があります。
この問題を解決するために、<u>動的配列</u>を使用してリストを実装できます。これは配列の利点を継承し、プログラム実行中に動的に拡張できます。
実際、**多くのプログラミング言語の標準ライブラリは動的配列を使用してリストを実装しています**。例えば、Pythonの`list`、Javaの`ArrayList`、C++の`vector`、C#の`List`などです。以下の議論では、「リスト」と「動的配列」を同義の概念として扱います。
## リストの一般的な操作
### リストの初期化
通常、「初期値なし」と「初期値あり」の2つの初期化方法を使用します。
=== "Python"
```python title="list.py"
# リストを初期化
# 初期値なし
nums1: list[int] = []
# 初期値あり
nums: list[int] = [1, 3, 2, 5, 4]
```
=== "C++"
```cpp title="list.cpp"
/* リストを初期化 */
// 注意: C++では、vectorがここで説明されているnumsに相当します
// 初期値なし
vector<int> nums1;
// 初期値あり
vector<int> nums = { 1, 3, 2, 5, 4 };
```
=== "Java"
```java title="list.java"
/* リストを初期化 */
// 初期値なし
List<Integer> nums1 = new ArrayList<>();
// 初期値あり(要素型はint[]のラッパークラスInteger[]である必要があります)
Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 };
List<Integer> nums = new ArrayList<>(Arrays.asList(numbers));
```
=== "C#"
```csharp title="list.cs"
/* リストを初期化 */
// 初期値なし
List<int> nums1 = [];
// 初期値あり
int[] numbers = [1, 3, 2, 5, 4];
List<int> nums = [.. numbers];
```
=== "Go"
```go title="list_test.go"
/* リストを初期化 */
// 初期値なし
nums1 := []int{}
// 初期値あり
nums := []int{1, 3, 2, 5, 4}
```
=== "Swift"
```swift title="list.swift"
/* リストを初期化 */
// 初期値なし
let nums1: [Int] = []
// 初期値あり
var nums = [1, 3, 2, 5, 4]
```
=== "JS"
```javascript title="list.js"
/* リストを初期化 */
// 初期値なし
const nums1 = [];
// 初期値あり
const nums = [1, 3, 2, 5, 4];
```
=== "TS"
```typescript title="list.ts"
/* リストを初期化 */
// 初期値なし
const nums1: number[] = [];
// 初期値あり
const nums: number[] = [1, 3, 2, 5, 4];
```
=== "Dart"
```dart title="list.dart"
/* リストを初期化 */
// 初期値なし
List<int> nums1 = [];
// 初期値あり
List<int> nums = [1, 3, 2, 5, 4];
```
=== "Rust"
```rust title="list.rs"
/* リストを初期化 */
// 初期値なし
let nums1: Vec<i32> = Vec::new();
// 初期値あり
let nums: Vec<i32> = vec![1, 3, 2, 5, 4];
```
=== "C"
```c title="list.c"
// Cは組み込みの動的配列を提供していません
```
=== "Kotlin"
```kotlin title="list.kt"
```
=== "Zig"
```zig title="list.zig"
// リストを初期化
var nums = std.ArrayList(i32).init(std.heap.page_allocator);
defer nums.deinit();
try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 });
```
### 要素へのアクセス
リストは本質的に配列であるため、$O(1)$時間で要素にアクセスし更新することができ、非常に効率的です。
=== "Python"
```python title="list.py"
# 要素にアクセス
num: int = nums[1] # インデックス1の要素にアクセス
# 要素を更新
nums[1] = 0 # インデックス1の要素を0に更新
```
=== "C++"
```cpp title="list.cpp"
/* 要素にアクセス */
int num = nums[1]; // インデックス1の要素にアクセス
/* 要素を更新 */
nums[1] = 0; // インデックス1の要素を0に更新
```
=== "Java"
```java title="list.java"
/* 要素にアクセス */
int num = nums.get(1); // インデックス1の要素にアクセス
/* 要素を更新 */
nums.set(1, 0); // インデックス1の要素を0に更新
```
=== "C#"
```csharp title="list.cs"
/* 要素にアクセス */
int num = nums[1]; // インデックス1の要素にアクセス
/* 要素を更新 */
nums[1] = 0; // インデックス1の要素を0に更新
```
=== "Go"
```go title="list_test.go"
/* 要素にアクセス */
num := nums[1] // インデックス1の要素にアクセス
/* 要素を更新 */
nums[1] = 0 // インデックス1の要素を0に更新
```
=== "Swift"
```swift title="list.swift"
/* 要素にアクセス */
let num = nums[1] // インデックス1の要素にアクセス
/* 要素を更新 */
nums[1] = 0 // インデックス1の要素を0に更新
```
=== "JS"
```javascript title="list.js"
/* 要素にアクセス */
const num = nums[1]; // インデックス1の要素にアクセス
/* 要素を更新 */
nums[1] = 0; // インデックス1の要素を0に更新
```
=== "TS"
```typescript title="list.ts"
/* 要素にアクセス */
const num: number = nums[1]; // インデックス1の要素にアクセス
/* 要素を更新 */
nums[1] = 0; // インデックス1の要素を0に更新
```
=== "Dart"
```dart title="list.dart"
/* 要素にアクセス */
int num = nums[1]; // インデックス1の要素にアクセス
/* 要素を更新 */
nums[1] = 0; // インデックス1の要素を0に更新
```
=== "Rust"
```rust title="list.rs"
/* 要素にアクセス */
let num: i32 = nums[1]; // インデックス1の要素にアクセス
/* 要素を更新 */
nums[1] = 0; // インデックス1の要素を0に更新
```
=== "C"
```c title="list.c"
// Cは組み込みの動的配列を提供していません
```
=== "Kotlin"
```kotlin title="list.kt"
```
=== "Zig"
```zig title="list.zig"
// 要素にアクセス
var num = nums.items[1]; // インデックス1の要素にアクセス
// 要素を更新
nums.items[1] = 0; // インデックス1の要素を0に更新
```
### 要素の挿入と削除
配列と比較して、リストは要素の追加と削除においてより柔軟性を提供します。リストの末尾への要素追加は$O(1)$操作ですが、リストの他の場所での要素の挿入と削除の効率は配列と同じままで、時間計算量は$O(n)$です。
=== "Python"
```python title="list.py"
# リストをクリア
nums.clear()
# 末尾に要素を追加
nums.append(1)
nums.append(3)
nums.append(2)
nums.append(5)
nums.append(4)
# 中間に要素を挿入
nums.insert(3, 6) # インデックス3に数値6を挿入
# 要素を削除
nums.pop(3) # インデックス3の要素を削除
```
=== "C++"
```cpp title="list.cpp"
/* リストをクリア */
nums.clear();
/* 末尾に要素を追加 */
nums.push_back(1);
nums.push_back(3);
nums.push_back(2);
nums.push_back(5);
nums.push_back(4);
/* 中間に要素を挿入 */
nums.insert(nums.begin() + 3, 6); // インデックス3に数値6を挿入
/* 要素を削除 */
nums.erase(nums.begin() + 3); // インデックス3の要素を削除
```
=== "Java"
```java title="list.java"
/* リストをクリア */
nums.clear();
/* 末尾に要素を追加 */
nums.add(1);
nums.add(3);
nums.add(2);
nums.add(5);
nums.add(4);
/* 中間に要素を挿入 */
nums.add(3, 6); // インデックス3に数値6を挿入
/* 要素を削除 */
nums.remove(3); // インデックス3の要素を削除
```
=== "C#"
```csharp title="list.cs"
/* リストをクリア */
nums.Clear();
/* 末尾に要素を追加 */
nums.Add(1);
nums.Add(3);
nums.Add(2);
nums.Add(5);
nums.Add(4);
/* 中間に要素を挿入 */
nums.Insert(3, 6);
/* 要素を削除 */
nums.RemoveAt(3);
```
=== "Go"
```go title="list_test.go"
/* リストをクリア */
nums = nil
/* 末尾に要素を追加 */
nums = append(nums, 1)
nums = append(nums, 3)
nums = append(nums, 2)
nums = append(nums, 5)
nums = append(nums, 4)
/* 中間に要素を挿入 */
nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // インデックス3に数値6を挿入
/* 要素を削除 */
nums = append(nums[:3], nums[4:]...) // インデックス3の要素を削除
```
=== "Swift"
```swift title="list.swift"
/* リストをクリア */
nums.removeAll()
/* 末尾に要素を追加 */
nums.append(1)
nums.append(3)
nums.append(2)
nums.append(5)
nums.append(4)
/* 中間に要素を挿入 */
nums.insert(6, at: 3) // インデックス3に数値6を挿入
/* 要素を削除 */
nums.remove(at: 3) // インデックス3の要素を削除
```
=== "JS"
```javascript title="list.js"
/* リストをクリア */
nums.length = 0;
/* 末尾に要素を追加 */
nums.push(1);
nums.push(3);
nums.push(2);
nums.push(5);
nums.push(4);
/* 中間に要素を挿入 */
nums.splice(3, 0, 6);
/* 要素を削除 */
nums.splice(3, 1);
```
=== "TS"
```typescript title="list.ts"
/* リストをクリア */
nums.length = 0;
/* 末尾に要素を追加 */
nums.push(1);
nums.push(3);
nums.push(2);
nums.push(5);
nums.push(4);
/* 中間に要素を挿入 */
nums.splice(3, 0, 6);
/* 要素を削除 */
nums.splice(3, 1);
```
=== "Dart"
```dart title="list.dart"
/* リストをクリア */
nums.clear();
/* 末尾に要素を追加 */
nums.add(1);
nums.add(3);
nums.add(2);
nums.add(5);
nums.add(4);
/* 中間に要素を挿入 */
nums.insert(3, 6); // インデックス3に数値6を挿入
/* 要素を削除 */
nums.removeAt(3); // インデックス3の要素を削除
```
=== "Rust"
```rust title="list.rs"
/* リストをクリア */
nums.clear();
/* 末尾に要素を追加 */
nums.push(1);
nums.push(3);
nums.push(2);
nums.push(5);
nums.push(4);
/* 中間に要素を挿入 */
nums.insert(3, 6); // インデックス3に数値6を挿入
/* 要素を削除 */
nums.remove(3); // インデックス3の要素を削除
```
=== "C"
```c title="list.c"
// Cは組み込みの動的配列を提供していません
```
=== "Kotlin"
```kotlin title="list.kt"
```
=== "Zig"
```zig title="list.zig"
// リストをクリア
nums.clearRetainingCapacity();
// 末尾に要素を追加
try nums.append(1);
try nums.append(3);
try nums.append(2);
try nums.append(5);
try nums.append(4);
// 中間に要素を挿入
try nums.insert(3, 6); // インデックス3に数値6を挿入
// 要素を削除
_ = nums.orderedRemove(3); // インデックス3の要素を削除
```
### リストの反復
配列と同様に、リストはインデックスを使用して反復することも、各要素を直接反復することもできます。
=== "Python"
```python title="list.py"
# インデックスでリストを反復
count = 0
for i in range(len(nums)):
count += nums[i]
# リスト要素を直接反復
for num in nums:
count += num
```
=== "C++"
```cpp title="list.cpp"
/* インデックスでリストを反復 */
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums[i];
}
/* リスト要素を直接反復 */
count = 0;
for (int num : nums) {
count += num;
}
```
=== "Java"
```java title="list.java"
/* インデックスでリストを反復 */
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums.get(i);
}
/* リスト要素を直接反復 */
for (int num : nums) {
count += num;
}
```
=== "C#"
```csharp title="list.cs"
/* インデックスでリストを反復 */
int count = 0;
for (int i = 0; i < nums.Count; i++) {
count += nums[i];
}
/* リスト要素を直接反復 */
count = 0;
foreach (int num in nums) {
count += num;
}
```
=== "Go"
```go title="list_test.go"
/* インデックスでリストを反復 */
count := 0
for i := 0; i < len(nums); i++ {
count += nums[i]
}
/* リスト要素を直接反復 */
count = 0
for _, num := range nums {
count += num
}
```
=== "Swift"
```swift title="list.swift"
/* インデックスでリストを反復 */
var count = 0
for i in nums.indices {
count += nums[i]
}
/* リスト要素を直接反復 */
count = 0
for num in nums {
count += num
}
```
=== "JS"
```javascript title="list.js"
/* インデックスでリストを反復 */
let count = 0;
for (let i = 0; i < nums.length; i++) {
count += nums[i];
}
/* リスト要素を直接反復 */
count = 0;
for (const num of nums) {
count += num;
}
```
=== "TS"
```typescript title="list.ts"
/* インデックスでリストを反復 */
let count = 0;
for (let i = 0; i < nums.length; i++) {
count += nums[i];
}
/* リスト要素を直接反復 */
count = 0;
for (const num of nums) {
count += num;
}
```
=== "Dart"
```dart title="list.dart"
/* インデックスでリストを反復 */
int count = 0;
for (var i = 0; i < nums.length; i++) {
count += nums[i];
}
/* リスト要素を直接反復 */
count = 0;
for (var num in nums) {
count += num;
}
```
=== "Rust"
```rust title="list.rs"
// インデックスでリストを反復
let mut _count = 0;
for i in 0..nums.len() {
_count += nums[i];
}
// リスト要素を直接反復
_count = 0;
for num in &nums {
_count += num;
}
```
=== "C"
```c title="list.c"
// Cは組み込みの動的配列を提供していません
```
=== "Kotlin"
```kotlin title="list.kt"
```
=== "Zig"
```zig title="list.zig"
// インデックスでリストを反復
var count: i32 = 0;
var i: i32 = 0;
while (i < nums.items.len) : (i += 1) {
count += nums[i];
}
// リスト要素を直接反復
count = 0;
for (nums.items) |num| {
count += num;
}
```
### リストの連結
新しいリスト`nums1`が与えられたとき、それを元のリストの末尾に追加できます。
=== "Python"
```python title="list.py"
# 2つのリストを連結
nums1: list[int] = [6, 8, 7, 10, 9]
nums += nums1 # nums1をnumsの末尾に連結
```
=== "C++"
```cpp title="list.cpp"
/* 2つのリストを連結 */
vector<int> nums1 = { 6, 8, 7, 10, 9 };
// nums1をnumsの末尾に連結
nums.insert(nums.end(), nums1.begin(), nums1.end());
```
=== "Java"
```java title="list.java"
/* 2つのリストを連結 */
List<Integer> nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));
nums.addAll(nums1); // nums1をnumsの末尾に連結
```
=== "C#"
```csharp title="list.cs"
/* 2つのリストを連結 */
List<int> nums1 = [6, 8, 7, 10, 9];
nums.AddRange(nums1); // nums1をnumsの末尾に連結
```
=== "Go"
```go title="list_test.go"
/* 2つのリストを連結 */
nums1 := []int{6, 8, 7, 10, 9}
nums = append(nums, nums1...) // nums1をnumsの末尾に連結
```
=== "Swift"
```swift title="list.swift"
/* 2つのリストを連結 */
let nums1 = [6, 8, 7, 10, 9]
nums.append(contentsOf: nums1) // nums1をnumsの末尾に連結
```
=== "JS"
```javascript title="list.js"
/* 2つのリストを連結 */
const nums1 = [6, 8, 7, 10, 9];
nums.push(...nums1); // nums1をnumsの末尾に連結
```
=== "TS"
```typescript title="list.ts"
/* 2つのリストを連結 */
const nums1: number[] = [6, 8, 7, 10, 9];
nums.push(...nums1); // nums1をnumsの末尾に連結
```
=== "Dart"
```dart title="list.dart"
/* 2つのリストを連結 */
List<int> nums1 = [6, 8, 7, 10, 9];
nums.addAll(nums1); // nums1をnumsの末尾に連結
```
=== "Rust"
```rust title="list.rs"
/* 2つのリストを連結 */
let nums1: Vec<i32> = vec![6, 8, 7, 10, 9];
nums.extend(nums1);
```
=== "C"
```c title="list.c"
// Cは組み込みの動的配列を提供していません
```
=== "Kotlin"
```kotlin title="list.kt"
```
=== "Zig"
```zig title="list.zig"
// 2つのリストを連結
var nums1 = std.ArrayList(i32).init(std.heap.page_allocator);
defer nums1.deinit();
try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 });
try nums.insertSlice(nums.items.len, nums1.items); // nums1をnumsの末尾に連結
```
### リストのソート
リストがソートされると、「二分探索」や「双ポインタ」アルゴリズムなど、配列関連のアルゴリズム問題でよく使用されるアルゴリズムを使用できます。
=== "Python"
```python title="list.py"
# リストをソート
nums.sort() # ソート後、リスト要素は昇順になります
```
=== "C++"
```cpp title="list.cpp"
/* リストをソート */
sort(nums.begin(), nums.end()); // ソート後、リスト要素は昇順になります
```
=== "Java"
```java title="list.java"
/* リストをソート */
Collections.sort(nums); // ソート後、リスト要素は昇順になります
```
=== "C#"
```csharp title="list.cs"
/* リストをソート */
nums.Sort(); // ソート後、リスト要素は昇順になります
```
=== "Go"
```go title="list_test.go"
/* リストをソート */
sort.Ints(nums) // ソート後、リスト要素は昇順になります
```
=== "Swift"
```swift title="list.swift"
/* リストをソート */
nums.sort() // ソート後、リスト要素は昇順になります
```
=== "JS"
```javascript title="list.js"
/* リストをソート */
nums.sort((a, b) => a - b); // ソート後、リスト要素は昇順になります
```
=== "TS"
```typescript title="list.ts"
/* リストをソート */
nums.sort((a, b) => a - b); // ソート後、リスト要素は昇順になります
```
=== "Dart"
```dart title="list.dart"
/* リストをソート */
nums.sort(); // ソート後、リスト要素は昇順になります
```
=== "Rust"
```rust title="list.rs"
/* リストをソート */
nums.sort(); // ソート後、リスト要素は昇順になります
```
=== "C"
```c title="list.c"
// Cは組み込みの動的配列を提供していません
```
=== "Kotlin"
```kotlin title="list.kt"
```
=== "Zig"
```zig title="list.zig"
// リストをソート
std.sort.sort(i32, nums.items, {}, comptime std.sort.asc(i32));
```
## リストの実装
多くのプログラミング言語には、Java、C++、Pythonなどを含む組み込みリストが付属しています。それらの実装は、初期容量や拡張係数などの様々なパラメータを慎重に考慮した設定で、複雑になりがちです。興味のある読者は、さらなる学習のためにソースコードを調べることができます。
リストがどのように動作するかの理解を深めるために、3つの重要な設計側面に焦点を当てて、簡略化されたリストの実装を試みます:
- **初期容量**:配列に合理的な初期容量を選択します。この例では、初期容量として10を選択します。
- **サイズ記録**:リスト内の現在の要素数を記録する変数`size`を宣言し、要素の挿入と削除でリアルタイムに更新します。この変数により、リストの末尾を特定し、拡張が必要かどうかを判断できます。
- **拡張メカニズム**:要素挿入時にリストが満杯に達した場合、拡張プロセスが必要です。これには拡張係数に基づいてより大きな配列を作成し、現在の配列からすべての要素を新しい配列に転送することが含まれます。この例では、拡張のたびに配列サイズを2倍にすることを規定します。
```src
[file]{my_list}-[class]{my_list}-[func]{}
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1,71 @@
# メモリとキャッシュ *
この章の最初の2つのセクションでは、「連続格納」と「分散格納」をそれぞれ表現する2つの基本的なデータ構造である配列と連結リストを探究しました。
実際、**物理構造はプログラムがメモリとキャッシュをどの程度効率的に利用するかを大きく決定し**、これがアルゴリズムの全体的なパフォーマンスに影響を与えます。
## コンピュータ記憶装置
コンピュータには3種類の記憶装置があります:<u>ハードディスク</u>、<u>ランダムアクセスメモリ(RAM</u>、および<u>キャッシュメモリ</u>です。以下の表は、コンピュータシステムにおけるそれぞれの役割とパフォーマンス特性を示しています。
<p align="center"> 表 <id> &nbsp; コンピュータ記憶装置 </p>
| | ハードディスク | メモリ | キャッシュ |
| ----------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| 用途 | OS、プログラム、ファイルなどのデータの長期保存 | 現在実行中のプログラムと処理中のデータの一時保存 | 頻繁にアクセスされるデータと命令を保存し、CPUのメモリへのアクセス数を削減 |
| 揮発性 | 電源オフ後もデータは失われない | 電源オフ後にデータは失われる | 電源オフ後にデータは失われる |
| 容量 | より大きい、TBレベル | より小さい、GBレベル | 非常に小さい、MBレベル |
| 速度 | より遅い、数百から数千MB/s | より高速、数十GB/s | 非常に高速、数十から数百GB/s |
| 価格(USD) | より安価、数セント/GB | より高価、数ドル/GB | 非常に高価、CPUと一緒に価格設定 |
コンピュータ記憶システムは、下図に示すようにピラミッドとして視覚化できます。ピラミッドの上部にある記憶装置ほど高速で、容量が小さく、より高価です。このマルチレベル設計は偶然ではなく、コンピュータ科学者とエンジニアによる慎重な検討の結果です。
- **ハードディスクをメモリに置き換えるのは困難です**。第一に、メモリ内のデータは電源オフ後に失われるため、長期データ保存には適していません。第二に、メモリはハードディスクよりも大幅に高価で、消費者市場での広範囲な使用の実現可能性を制限しています。
- **キャッシュは大容量と高速のトレードオフに直面しています**。L1、L2、L3キャッシュの容量が増加するにつれて、その物理サイズが大きくなり、CPUコアからの距離が増加します。これによりデータ転送時間が長くなり、アクセス遅延が高くなります。現在の技術では、マルチレベルキャッシュ構造が容量、速度、コストの間の最適なバランスを提供します。
![コンピュータ記憶システム](ram_and_cache.assets/storage_pyramid.png)
!!! tip
コンピュータの記憶階層は、速度、容量、コストの間の慎重なバランスを反映しています。このタイプのトレードオフは様々な業界で一般的であり、利益と制限の間の最適なバランスを見つけることが重要です。
全体的に、**ハードディスクは大量のデータの長期保存を提供し、メモリはプログラム実行中に処理されるデータの一時保存として機能し、キャッシュは頻繁にアクセスされるデータと命令を保存して実行効率を向上させます**。それらは一緒になってコンピュータシステムの効率的な動作を保証します。
下図に示すように、プログラム実行中、データはハードディスクからメモリに読み込まれ、CPU計算が行われます。CPUの拡張として機能するキャッシュは、**メモリからインテリジェントにデータを先読み**し、CPUのより高速なデータアクセスを可能にします。これによりプログラム実行効率が大幅に向上し、低速なメモリへの依存が減少します。
![ハードディスク、メモリ、キャッシュ間のデータフロー](ram_and_cache.assets/computer_storage_devices.png)
## データ構造のメモリ効率
メモリ空間利用の観点から、配列と連結リストにはそれぞれ利点と制限があります。
一方で、**メモリは限られており、複数のプログラム間で共有できない**ため、データ構造での空間使用の最適化は重要です。配列は要素が密接にパックされており、連結リストのように参照(ポインタ)のための追加メモリを必要としないため、空間効率的です。しかし、配列は連続したメモリブロックを事前に割り当てる必要があり、割り当てられた空間が実際の必要量を超える場合、無駄につながる可能性があります。配列の拡張も追加の時間と空間のオーバーヘッドを伴います。対照的に、連結リストは各ノードに対してメモリを動的に割り当て・解放し、ポインタのための追加メモリのコストでより大きな柔軟性を提供します。
一方で、プログラム実行中、**繰り返されるメモリの割り当てと解放はメモリの断片化を増加させ**、メモリ利用効率を低下させます。配列は連続記憶方式により、メモリ断片化を引き起こす可能性が比較的低いです。対照的に、連結リストは要素を非連続の場所に保存し、頻繁な挿入と削除はメモリ断片化を悪化させる可能性があります。
## データ構造のキャッシュ効率
キャッシュはメモリよりも空間容量がはるかに小さいですが、はるかに高速で、プログラム実行速度において重要な役割を果たします。限られた容量のため、キャッシュは頻繁にアクセスされるデータのサブセットのみを保存できます。CPUがキャッシュに存在しないデータにアクセスしようとすると、<u>キャッシュミス</u>が発生し、CPUは低速なメモリから必要なデータを取得する必要があり、パフォーマンスに影響を与える可能性があります。
明らかに、**キャッシュミスが少ないほど、CPUのデータ読み書き効率が高く**、プログラムパフォーマンスが向上します。CPUがキャッシュからデータを正常に取得する割合は<u>キャッシュヒット率</u>と呼ばれ、キャッシュ効率を測定するためによく使用される指標です。
より高い効率を達成するために、キャッシュは以下のデータロードメカニズムを採用します。
- **キャッシュライン**:キャッシュは個々のバイトではなく、キャッシュラインと呼ばれる単位でデータを保存・ロードして動作します。このアプローチは、一度により大きなデータブロックを転送することで効率を向上させます。
- **先読みメカニズム**:プロセッサはデータアクセスパターン(例:連続または固定ストライドアクセス)を予測し、これらのパターンに基づいてデータをキャッシュに先読みして、キャッシュヒット率を向上させます。
- **空間的局所性**:特定のデータがアクセスされると、近くのデータもまもなくアクセスされる可能性があります。これを活用するために、キャッシュは要求されたデータと一緒に隣接するデータをロードし、ヒット率を向上させます。
- **時間的局所性**:データがアクセスされた場合、近い将来に再びアクセスされる可能性があります。キャッシュはこの原理を使用して、最近アクセスされたデータを保持してヒット率を向上させます。
実際、**配列と連結リストは異なるキャッシュ利用効率を持ち**、これは主に以下の側面に反映されます。
- **占有空間**:連結リスト要素は配列要素よりも多くの空間を占有するため、キャッシュに保持される有効データが少なくなります。
- **キャッシュライン**:連結リストデータはメモリ全体に散在し、キャッシュは「行単位でロード」されるため、ロードされる無効データの割合が高くなります。
- **先読みメカニズム**:配列のデータアクセスパターンは連結リストよりも「予測可能」で、つまりシステムがこれからロードされるデータを推測しやすいです。
- **空間的局所性**:配列は連続したメモリ空間に保存されるため、ロードされているデータの近くのデータがまもなくアクセスされる可能性が高くなります。
全体的に、**配列はより高いキャッシュヒット率を持ち、一般的に連結リストよりも操作効率が高いです**。これにより、配列に基づくデータ構造はアルゴリズム問題の解決において人気があります。
**高いキャッシュ効率が配列が常に連結リストより優れているという意味ではない**ことに注意すべきです。データ構造の選択は特定のアプリケーション要件に依存すべきです。例えば、配列と連結リストの両方が「スタック」データ構造を実装できますが(次章で詳細説明)、それらは異なるシナリオに適しています。
- アルゴリズム問題では、より高い操作効率とランダムアクセス機能を提供するため、配列に基づくスタックを選択する傾向があります。唯一のコストは配列に対して一定量のメモリ空間を事前に割り当てる必要があることです。
- データ量が非常に大きく、高度に動的で、スタックの予想サイズを推定するのが困難な場合、連結リストに基づくスタックがより良い選択です。連結リストは大量のデータをメモリの異なる部分に分散でき、配列拡張の追加オーバーヘッドを回避できます。
@@ -0,0 +1,81 @@
# まとめ
### 重要な復習
- 配列と連結リストは2つの基本的なデータ構造であり、コンピュータメモリにおける2つの格納方法を表しています:連続空間格納と非連続空間格納です。それらの特性は互いに補完し合います。
- 配列はランダムアクセスをサポートし、使用するメモリが少ない一方で、要素の挿入と削除は非効率的で、初期化後の長さが固定されています。
- 連結リストは参照(ポインタ)の変更によって効率的なノードの挿入と削除を実装し、長さを柔軟に調整できますが、ノードアクセス効率が低く、より多くのメモリを消費します。
- 連結リストの一般的な種類には、単方向連結リスト、循環連結リスト、双方向連結リストがあり、それぞれに独自の応用シナリオがあります。
- リストは要素の順序付けられたコレクションで、追加、削除、変更をサポートし、通常は動的配列に基づいて実装され、配列の利点を保持しながら柔軟な長さ調整を可能にします。
- リストの出現により配列の実用性が大幅に向上しましたが、一部のメモリ空間の無駄につながる可能性があります。
- プログラム実行中、データは主にメモリに格納されます。配列はより高いメモリ空間効率を提供し、連結リストはメモリ使用においてより柔軟です。
- キャッシュは、キャッシュライン、先読み、空間的局所性、時間的局所性などのメカニズムを通じてCPUに高速データアクセスを提供し、プログラム実行効率を大幅に向上させます。
- より高いキャッシュヒット率により、配列は一般的に連結リストよりも効率的です。データ構造を選択する際は、特定のニーズとシナリオに基づいて適切な選択をすべきです。
### Q & A
**Q**:配列をスタックに格納するかヒープに格納するかは、時間と空間効率に影響しますか?
スタックとヒープの両方に格納される配列は連続したメモリ空間に格納され、データ操作効率は本質的に同じです。しかし、スタックとヒープには独自の特性があり、以下の違いが生じます。
1. 割り当てと解放効率:スタックはより小さなメモリブロックで、コンパイラによって自動的に割り当てられます。ヒープメモリは比較的大きく、コードで動的に割り当てることができ、断片化しやすいです。したがって、ヒープでの割り当てと解放操作は一般的にスタックよりも遅くなります。
2. サイズ制限:スタックメモリは比較的小さく、ヒープサイズは一般的に利用可能なメモリによって制限されます。したがって、ヒープは大きな配列の格納により適しています。
3. 柔軟性:スタック上の配列のサイズはコンパイル時に決定される必要がありますが、ヒープ上の配列のサイズは実行時に動的に決定できます。
**Q**:なぜ配列は同じ型の要素を必要とし、連結リストは同じ型の要素を強調しないのですか?
連結リストは参照(ポインタ)によって接続されたノードで構成され、各ノードはint、double、string、objectなど、異なる型のデータを格納できます。
対照的に、配列要素は同じ型である必要があり、これにより対応する要素位置にアクセスするためのオフセットを計算できます。例えば、intとlong型の両方を含む配列で、単一要素がそれぞれ4バイトと8バイトを占有する場合、配列に2つの異なる長さの要素が含まれているため、以下の式を使用してオフセットを計算できません。
```shell
# 要素メモリアドレス = 配列メモリアドレス + 要素長 * 要素インデックス
```
**Q**:ノードを削除した後、`P.next``None`に設定する必要がありますか?
`P.next`を変更しなくても問題ありません。連結リストの観点から、ヘッドノードからテールノードまでの巡回で`P`に遭遇することはもうありません。これは、ノード`P`がリストから効果的に削除されたことを意味し、`P`が指す場所はもはやリストに影響しません。
ガベージコレクションの観点から、Java、Python、Goなどの自動ガベージコレクションメカニズムを持つ言語では、ノード`P`が収集されるかどうかは、それを指す参照がまだあるかどうかに依存し、`P.next`の値には依存しません。CやC++などの言語では、ノードのメモリを手動で解放する必要があります。
**Q**:連結リストでは、挿入と削除操作の時間計算量は`O(1)`です。しかし、挿入や削除前の要素検索には`O(n)`時間がかかるので、なぜ時間計算量は`O(n)`ではないのですか?
要素を最初に検索してから削除する場合、時間計算量は確かに`O(n)`です。しかし、連結リストの挿入と削除における`O(1)`の利点は他のアプリケーションで実現できます。例えば、連結リストを使用した両端キューの実装では、常にヘッドとテールノードを指すポインタを維持し、各挿入と削除操作を`O(1)`にします。
**Q**:「連結リストの定義と格納方法」の図で、薄青色の格納ノードは単一のメモリアドレスを占有しますか、それともノード値と半分を共有しますか?
図は単なる定性的な表現であり、定量的分析は特定の状況に依存します。
- 異なる型のノード値は異なる量の空間を占有します。例えば、int、long、double、オブジェクトインスタンスです。
- ポインタ変数によって占有されるメモリ空間は、使用されるオペレーティングシステムとコンパイル環境に依存し、通常8バイトまたは4バイトです。
**Q**:リストの末尾への要素追加は常に`O(1)`ですか?
要素を追加することでリスト長を超える場合、リストは最初に拡張される必要があります。システムは新しいメモリブロックを要求し、元のリストのすべての要素を移動するため、この場合の時間計算量は`O(n)`になります。
**Q**:「リストの出現により配列の実用性が大幅に向上しましたが、一部のメモリ空間の無駄につながる可能性があります」という文は、容量、長さ、拡張係数などの追加変数によって占有されるメモリを指していますか?
ここでの空間の無駄は主に2つの側面を指します:一方で、リストは初期長で設定されますが、常に必要とは限りません。他方で、頻繁な拡張を防ぐため、拡張は通常$\times 1.5$などの係数で乗算されます。これにより多くの空きスロットが生まれ、通常は完全に埋めることができません。
**Q**Pythonで`n = [1, 2, 3]`を初期化した後、これら3つの要素のアドレスは連続していますが、`m = [2, 1, 3]`を初期化すると、各要素の`id`は連続していないが`n`のものと同一です。これらの要素のアドレスが連続していない場合、`m`はまだ配列ですか?
リスト要素を連結リストノード`n = [n1, n2, n3, n4, n5]`に置き換える場合、これら5つのノードオブジェクトも通常メモリ全体に分散しています。しかし、リストインデックスが与えられれば、`O(1)`時間でノードのメモリアドレスにアクセスでき、対応するノードにアクセスできます。これは、配列がノード自体ではなく、ノードへの参照を格納するためです。
多くの言語とは異なり、Pythonでは数値もオブジェクトとしてラップされ、リストは数値自体ではなく、これらの数値への参照を格納します。したがって、2つの配列の同じ数値が同じ`id`を持ち、これらの数値のメモリアドレスは連続である必要がないことがわかります。
**Q**C++ STLの`std::list`はすでに双方向連結リストを実装していますが、一部のアルゴリズム書籍では直接使用していないようです。何か制限がありますか?
一方で、アルゴリズムを実装する際は配列を使用することを好み、必要な場合のみ連結リストを使用します。主に2つの理由があります。
- 空間オーバーヘッド:各要素に2つの追加ポインタ(前の要素用と次の要素用)が必要なため、`std::list`は通常`std::vector`よりも多くの空間を占有します。
- キャッシュ非友好的:データが連続して格納されていないため、`std::list`はキャッシュ利用率が低くなります。一般的に、`std::vector`の方がパフォーマンスが優れています。
他方で、連結リストは主に二分木とグラフに必要です。スタックとキューは、連結リストではなく、プログラミング言語の`stack``queue`クラスを使用して実装されることが多いです。
**Q**:リスト`res = [0] * self.size()`を初期化すると、`res`の各要素は同じアドレスを参照しますか?
いいえ。しかし、この問題は二次元配列で発生します。例えば、二次元リスト`res = [[0]] * self.size()`を初期化すると、同じリスト`[0]`を複数回参照することになります。
**Q**:ノードを削除する際、その後続ノードへの参照を断つ必要がありますか?
データ構造とアルゴリズム(問題解決)の観点から、プログラムのロジックが正しい限り、リンクを断たなくても問題ありません。標準ライブラリの観点から、リンクを断つ方が安全で論理的に明確です。リンクを断たず、削除されたノードが適切にリサイクルされない場合、後続ノードのメモリのリサイクルに影響を与える可能性があります。