mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-02 02:24:24 +00:00
Re-translate the Japanese version (#1871)
* Retranslate Japanese docs with GPT-5.4 * Retranslate Japanese code with GPT-5.4
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
# 配列
|
||||
|
||||
<u>配列</u>は線形データ構造で、同じような項目が並んでいるようなもので、コンピュータのメモリ内の連続した空間に一緒に格納されます。これは整理された格納を維持するシーケンスのようなものです。この並びの各項目には、<u>インデックス</u>として知られる独自の「位置」があります。以下の図を参照して、配列の動作を観察し、これらの重要な用語を理解してください。
|
||||
<u>配列(array)</u>は線形データ構造の一種であり、同じ型の要素を連続したメモリ領域に格納します。要素が配列内にある位置を、その要素の<u>インデックス(index)</u>と呼びます。下図は、配列の主要な概念と格納方式を示しています。
|
||||
|
||||

|
||||

|
||||
|
||||
## 配列の一般的な操作
|
||||
|
||||
### 配列の初期化
|
||||
|
||||
配列は必要に応じて2つの方法で初期化できます:初期値なしまたは指定された初期値付きです。初期値が指定されていない場合、ほとんどのプログラミング言語は配列要素を$0$に設定します:
|
||||
必要に応じて、配列の初期化方法として初期値なしと初期値ありの 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]
|
||||
```
|
||||
@@ -21,11 +21,11 @@
|
||||
=== "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 };
|
||||
```
|
||||
@@ -33,7 +33,7 @@
|
||||
=== "Java"
|
||||
|
||||
```java title="array.java"
|
||||
/* 配列を初期化 */
|
||||
/* 配列を初期化する */
|
||||
int[] arr = new int[5]; // { 0, 0, 0, 0, 0 }
|
||||
int[] nums = { 1, 3, 2, 5, 4 };
|
||||
```
|
||||
@@ -41,7 +41,7 @@
|
||||
=== "C#"
|
||||
|
||||
```csharp title="array.cs"
|
||||
/* 配列を初期化 */
|
||||
/* 配列を初期化する */
|
||||
int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ]
|
||||
int[] nums = [1, 3, 2, 5, 4];
|
||||
```
|
||||
@@ -49,18 +49,18 @@
|
||||
=== "Go"
|
||||
|
||||
```go title="array.go"
|
||||
/* 配列を初期化 */
|
||||
/* 配列を初期化する */
|
||||
var arr [5]int
|
||||
// Goでは、長さを指定([5]int)すると配列を示し、指定しない([]int)とスライスを示します。
|
||||
// Goの配列はコンパイル時に固定長を持つよう設計されているため、長さの指定には定数のみ使用できます。
|
||||
// extend()メソッドの実装の便宜上、ここではSliceを配列として扱います。
|
||||
// Go では、長さを指定する場合([5]int)は配列であり、長さを指定しない場合([]int)はスライス
|
||||
// Go の配列はコンパイル時に長さが確定するよう設計されているため、長さの指定には定数しか使用できない
|
||||
// 拡張 extend() メソッドを実装しやすくするため、以下ではスライス(Slice)を配列(Array)として扱う
|
||||
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]
|
||||
```
|
||||
@@ -68,7 +68,7 @@
|
||||
=== "JS"
|
||||
|
||||
```javascript title="array.js"
|
||||
/* 配列を初期化 */
|
||||
/* 配列を初期化する */
|
||||
var arr = new Array(5).fill(0);
|
||||
var nums = [1, 3, 2, 5, 4];
|
||||
```
|
||||
@@ -76,7 +76,7 @@
|
||||
=== "TS"
|
||||
|
||||
```typescript title="array.ts"
|
||||
/* 配列を初期化 */
|
||||
/* 配列を初期化する */
|
||||
let arr: number[] = new Array(5).fill(0);
|
||||
let nums: number[] = [1, 3, 2, 5, 4];
|
||||
```
|
||||
@@ -84,7 +84,7 @@
|
||||
=== "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];
|
||||
```
|
||||
@@ -92,20 +92,20 @@
|
||||
=== "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()メソッドの実装の便宜上、ここではベクターを配列として扱います。
|
||||
// Rust では、長さを指定する場合([i32; 5])は配列であり、長さを指定しない場合(&[i32])はスライス
|
||||
// Rust の配列はコンパイル時に長さが確定するよう設計されているため、長さの指定には定数しか使用できない
|
||||
// Vector は Rust で一般に動的配列として使われる型
|
||||
// 拡張 extend() メソッドを実装しやすくするため、以下では vector を配列(array)として扱う
|
||||
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 };
|
||||
```
|
||||
@@ -113,18 +113,32 @@
|
||||
=== "Kotlin"
|
||||
|
||||
```kotlin title="array.kt"
|
||||
|
||||
/* 配列を初期化する */
|
||||
var arr = IntArray(5) // { 0, 0, 0, 0, 0 }
|
||||
var nums = intArrayOf(1, 3, 2, 5, 4)
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="array.rb"
|
||||
# 配列を初期化する
|
||||
arr = Array.new(5, 0)
|
||||
nums = [1, 3, 2, 5, 4]
|
||||
```
|
||||
|
||||
??? pythontutor "実行の可視化"
|
||||
|
||||
https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0Aarr%20%3D%20%5B0%5D%20*%205%20%20%23%20%5B%200,%200,%200,%200,%200%20%5D%0Anums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
|
||||
|
||||
### 要素へのアクセス
|
||||
|
||||
配列内の要素は連続したメモリ空間に格納されるため、各要素のメモリアドレスを計算することが簡単になります。以下の図に示されている公式は、配列のメモリアドレス(特に、最初の要素のアドレス)と要素のインデックスを利用して、要素のメモリアドレスを決定するのに役立ちます。この計算により、目的の要素への直接アクセスが合理化されます。
|
||||
配列要素は連続したメモリ領域に格納されるため、要素のメモリアドレスの計算は非常に容易です。配列のメモリアドレス(先頭要素のメモリアドレス)とある要素のインデックスが与えられれば、下図の式を使ってその要素のメモリアドレスを計算でき、直接その要素にアクセスできます。
|
||||
|
||||

|
||||

|
||||
|
||||
上の図で観察されるように、配列のインデックスは慣例的に$0$から始まります。これは直感に反するように見えるかもしれません。数を数えるのは通常$1$から始まるためですが、アドレス計算公式内では、**インデックスは本質的にメモリアドレスからのオフセット**です。最初の要素のアドレスでは、このオフセットは$0$で、そのインデックスが$0$であることを検証しています。
|
||||
上図を見ると、配列の最初の要素のインデックスは $0$ であり、これは少し直感に反するように思えます。というのも、$1$ から数え始めるほうが自然だからです。しかし、アドレス計算式の観点では、**インデックスの本質はメモリアドレスのオフセット**です。先頭要素のアドレスのオフセットは $0$ であるため、そのインデックスが $0$ なのは妥当です。
|
||||
|
||||
配列内の要素へのアクセスは非常に効率的で、$O(1)$時間で任意の要素にランダムアクセスできます。
|
||||
配列では要素へのアクセスは非常に効率的であり、$O(1)$ 時間で任意の要素にランダムアクセスできます。
|
||||
|
||||
```src
|
||||
[file]{array}-[class]{}-[func]{random_access}
|
||||
@@ -132,11 +146,11 @@
|
||||
|
||||
### 要素の挿入
|
||||
|
||||
配列要素はメモリ内で密に詰まっており、それらの間に追加データを収容するための空間はありません。以下の図に示すように、配列の中央に要素を挿入するには、後続のすべての要素を1つずつ後ろにシフトして、新しい要素のための空間を作る必要があります。
|
||||
配列要素はメモリ内で「ぴったり隣接して」おり、その間にほかのデータを格納する余地はありません。下図のように、配列の途中に要素を挿入したい場合は、その要素より後ろにあるすべての要素を 1 つずつ後ろへずらし、その後でそのインデックスに要素を代入する必要があります。
|
||||
|
||||

|
||||

|
||||
|
||||
配列の長さが固定されているため、要素を挿入すると必然的に配列の最後の要素が失われることに注意することが重要です。この問題を解決する方法は「リスト」の章で探求されます。
|
||||
注意すべき点として、配列の長さは固定であるため、要素を 1 つ挿入すると配列末尾の要素が必ず「失われ」ます。この問題の解決策は「リスト」の章で扱います。
|
||||
|
||||
```src
|
||||
[file]{array}-[class]{}-[func]{insert}
|
||||
@@ -144,25 +158,25 @@
|
||||
|
||||
### 要素の削除
|
||||
|
||||
同様に、以下の図に示すように、インデックス$i$の要素を削除するには、インデックス$i$に続くすべての要素を1つずつ前に移動する必要があります。
|
||||
同様に、下図のように、インデックス $i$ の要素を削除したい場合は、インデックス $i$ より後ろの要素をすべて 1 つずつ前へずらす必要があります。
|
||||
|
||||

|
||||

|
||||
|
||||
削除後、元の最後の要素は「意味がない」ものになるため、特定の修正は必要ないことに注意してください。
|
||||
注意してください。要素の削除が完了すると、もともとの末尾要素は「意味を持たない」状態になるため、わざわざ変更する必要はありません。
|
||||
|
||||
```src
|
||||
[file]{array}-[class]{}-[func]{remove}
|
||||
```
|
||||
|
||||
要約すると、配列の挿入と削除操作には以下の欠点があります:
|
||||
全体として見ると、配列の挿入と削除には次の欠点があります。
|
||||
|
||||
- **高い時間計算量**:配列の挿入と削除の両方の平均時間計算量は$O(n)$で、ここで$n$は配列の長さです。
|
||||
- **要素の損失**:配列の長さが固定されているため、挿入時に配列の容量を超える要素は失われます。
|
||||
- **メモリの無駄**:より長い配列を初期化して前部分のみを利用すると、挿入時に「意味のない」末尾要素が生じ、メモリ空間の無駄につながります。
|
||||
- **時間計算量が高い**:配列の挿入と削除の平均時間計算量はいずれも $O(n)$ であり、ここで $n$ は配列長です。
|
||||
- **要素が失われる**:配列の長さは不変であるため、要素を挿入すると配列長の範囲を超えた要素は失われます。
|
||||
- **メモリの浪費**:やや長めの配列を初期化して先頭部分だけを使うこともでき、この場合データ挿入時に失われる末尾要素はすべて「無意味」ですが、その代わり一部のメモリ領域が無駄になります。
|
||||
|
||||
### 配列の走査
|
||||
|
||||
ほとんどのプログラミング言語では、インデックスを使用するか、各要素を直接反復することで配列を走査できます:
|
||||
ほとんどのプログラミング言語では、インデックスを使って配列を走査することも、各要素を直接取り出しながら走査することもできます。
|
||||
|
||||
```src
|
||||
[file]{array}-[class]{}-[func]{traverse}
|
||||
@@ -170,9 +184,9 @@
|
||||
|
||||
### 要素の検索
|
||||
|
||||
配列内の特定の要素を見つけることは、配列を反復し、各要素をチェックして目的の値と一致するかどうかを決定することを含みます。
|
||||
配列内で指定した要素を探すには、配列を走査し、各反復で要素値が一致するかを判定し、一致したら対応するインデックスを出力します。
|
||||
|
||||
配列は線形データ構造であるため、この操作は一般的に「線形探索」と呼ばれます。
|
||||
配列は線形データ構造であるため、上記の検索操作は「線形探索」と呼ばれます。
|
||||
|
||||
```src
|
||||
[file]{array}-[class]{}-[func]{find}
|
||||
@@ -180,34 +194,34 @@
|
||||
|
||||
### 配列の拡張
|
||||
|
||||
複雑なシステム環境では、安全な容量拡張のために配列の後にメモリ空間の可用性を確保することが困難になります。その結果、ほとんどのプログラミング言語では、**配列の長さは不変**です。
|
||||
複雑なシステム環境では、配列の後方にあるメモリ領域が利用可能であることをプログラム側で保証できず、そのため安全に配列容量を拡張できません。したがって、ほとんどのプログラミング言語では、**配列の長さは不変です**。
|
||||
|
||||
配列を拡張するには、より大きな配列を作成し、元の配列から要素をコピーする必要があります。この操作の時間計算量は$O(n)$で、大きな配列では時間がかかる可能性があります。コードは以下の通りです:
|
||||
配列を拡張したい場合は、より大きな新しい配列を作り、元の配列の要素を順に新配列へコピーする必要があります。これは $O(n)$ の操作であり、配列が大きい場合は非常に時間がかかります。コードは次のとおりです。
|
||||
|
||||
```src
|
||||
[file]{array}-[class]{}-[func]{extend}
|
||||
```
|
||||
|
||||
## 配列の利点と制限
|
||||
## 配列の利点と限界
|
||||
|
||||
配列は連続したメモリ空間に格納され、同じ型の要素で構成されます。このアプローチは、システムがデータ構造操作の効率を最適化するために活用できる実質的な事前情報を提供します。
|
||||
配列は連続したメモリ領域に格納され、要素の型も同一です。この方法には豊富な事前情報が含まれており、システムはそれらを利用してデータ構造の操作効率を最適化できます。
|
||||
|
||||
- **高い空間効率**:配列はデータのための連続したメモリブロックを割り当て、追加の構造的オーバーヘッドの必要性を排除します。
|
||||
- **ランダムアクセスのサポート**:配列は任意の要素への$O(1)$時間アクセスを可能にします。
|
||||
- **キャッシュ局所性**:配列要素にアクセスするとき、コンピュータはそれらを読み込むだけでなく、周囲のデータもキャッシュし、高速キャッシュを利用して後続の操作速度を向上させます。
|
||||
- **空間効率が高い**:配列はデータに連続したメモリブロックを割り当てるため、追加の構造オーバーヘッドが不要です。
|
||||
- **ランダムアクセスをサポートする**:配列では任意の要素に $O(1)$ 時間でアクセスできます。
|
||||
- **キャッシュ局所性**:配列要素にアクセスする際、コンピュータはその要素だけでなく周囲のデータもキャッシュするため、高速キャッシュを利用して後続操作の実行速度を高められます。
|
||||
|
||||
しかし、連続空間格納は諸刃の剣で、以下の制限があります:
|
||||
連続領域への格納は諸刃の剣であり、次のような制約があります。
|
||||
|
||||
- **挿入と削除の効率が低い**:配列に多くの要素が蓄積されると、要素の挿入や削除には大量の要素をシフトする必要があります。
|
||||
- **固定長**:配列の長さは初期化後に固定されます。配列を拡張するには、すべてのデータを新しい配列にコピーする必要があり、大きなコストがかかります。
|
||||
- **空間の無駄**:割り当てられた配列サイズが必要以上に大きい場合、余分な空間が無駄になります。
|
||||
- **挿入と削除の効率が低い**:配列内の要素が多い場合、挿入や削除では大量の要素を移動する必要があります。
|
||||
- **長さが不変**:配列は初期化後に長さが固定され、拡張するにはすべてのデータを新しい配列へコピーする必要があり、コストが大きくなります。
|
||||
- **空間の浪費**:配列に割り当てたサイズが実際の必要量を上回る場合、余分な領域は無駄になります。
|
||||
|
||||
## 配列の典型的な応用
|
||||
|
||||
配列は基本的で広く使用されるデータ構造です。様々なアルゴリズムで頻繁に応用され、複雑なデータ構造の実装に役立ちます。
|
||||
配列は基礎的で一般的なデータ構造であり、さまざまなアルゴリズムで頻繁に使われるだけでなく、多様な複雑データ構造の実装にも利用できます。
|
||||
|
||||
- **ランダムアクセス**:配列はランダムサンプリングが必要なときのデータ格納に理想的です。インデックスに基づいてランダムシーケンスを生成することで、効率的にランダムサンプリングを実現できます。
|
||||
- **ソートと検索**:配列はソートと検索アルゴリズムで最も一般的に使用されるデータ構造です。クイックソート、マージソート、二分探索などの技術は主に配列で動作します。
|
||||
- **ルックアップテーブル**:配列は迅速な要素や関係の取得のための効率的なルックアップテーブルとして機能します。例えば、文字をASCIIコードにマッピングすることは、ASCIIコード値をインデックスとして使用し、対応する要素を配列に格納することで簡単になります。
|
||||
- **機械学習**:ニューラルネットワークの領域では、配列はベクトル、行列、テンソルを含む重要な線形代数演算の実行において重要な役割を果たします。配列はニューラルネットワークプログラミングにおいて主要かつ最も広範囲に使用されるデータ構造として機能します。
|
||||
- **データ構造の実装**:配列は、スタック、キュー、ハッシュ表、ヒープ、グラフなど、様々なデータ構造を実装するための構成要素として機能します。例えば、グラフの隣接行列表現は本質的に二次元配列です。
|
||||
- **ランダムアクセス**:いくつかのサンプルをランダムに抽出したい場合、配列に格納してランダムな系列を生成し、インデックスに基づいてランダムサンプリングを行えます。
|
||||
- **ソートと探索**:配列はソートアルゴリズムと探索アルゴリズムで最もよく使われるデータ構造です。クイックソート、マージソート、二分探索などは主に配列上で行われます。
|
||||
- **ルックアップテーブル**:ある要素やその対応関係を高速に調べる必要がある場合、配列をルックアップテーブルとして使えます。たとえば文字から ASCII コードへの対応を実装したいなら、文字の ASCII コード値をインデックスとし、対応する要素を配列の対応位置に格納できます。
|
||||
- **機械学習**:ニューラルネットワークでは、ベクトル、行列、テンソル間の線形代数演算が大量に使われ、これらのデータはいずれも配列の形で構築されます。配列はニューラルネットワークプログラミングで最もよく使われるデータ構造です。
|
||||
- **データ構造の実装**:配列はスタック、キュー、ハッシュテーブル、ヒープ、グラフなどのデータ構造の実装に利用できます。たとえば、グラフの隣接行列表現は実際には 2 次元配列です。
|
||||
|
||||
Reference in New Issue
Block a user