mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-07 11:36:46 +08:00
build
This commit is contained in:
178
ja/docs/chapter_data_structure/basic_data_types.md
Normal file
178
ja/docs/chapter_data_structure/basic_data_types.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 3.2 基本データ型
|
||||
|
||||
コンピュータ内のデータについて考える際、テキスト、画像、動画、音声、3Dモデルなど、様々な形式が思い浮かびます。これらの組織的な形式は異なりますが、すべて様々な基本データ型から構成されています。
|
||||
|
||||
**基本データ型とは、CPUが直接操作できるもの**であり、アルゴリズムで直接使用されます。主に以下が含まれます。
|
||||
|
||||
- 整数型:`byte`、`short`、`int`、`long`
|
||||
- 浮動小数点型:`float`、`double`、小数を表現するために使用
|
||||
- 文字型:`char`、様々な言語の文字、句読点、さらには絵文字を表現するために使用
|
||||
- ブール型:`bool`、「はい」または「いいえ」の判断を表現するために使用
|
||||
|
||||
**基本データ型は、コンピュータ内で二進形式で格納されます**。1つの二進桁は1ビットです。ほとんどの現代的なオペレーティングシステムでは、1バイトは8ビットで構成されています。
|
||||
|
||||
基本データ型の値の範囲は、それらが占める空間のサイズに依存します。以下では、Javaを例に説明します。
|
||||
|
||||
- 整数型`byte`は1バイト = 8ビットを占め、$2^8$個の数値を表現できます。
|
||||
- 整数型`int`は4バイト = 32ビットを占め、$2^{32}$個の数値を表現できます。
|
||||
|
||||
以下の表は、Javaにおける様々な基本データ型が占める空間、値の範囲、デフォルト値を示しています。この表を暗記する必要はありませんが、一般的な理解を持ち、必要時に参照することをお勧めします。
|
||||
|
||||
<p align="center"> 表 3-1 基本データ型が占める空間と値の範囲 </p>
|
||||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
| 型 | シンボル | 占有空間 | 最小値 | 最大値 | デフォルト値 |
|
||||
| ------- | -------- | -------- | ------------------------ | ----------------------- | -------------- |
|
||||
| 整数 | `byte` | 1バイト | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | 0 |
|
||||
| | `short` | 2バイト | $-2^{15}$ | $2^{15} - 1$ | 0 |
|
||||
| | `int` | 4バイト | $-2^{31}$ | $2^{31} - 1$ | 0 |
|
||||
| | `long` | 8バイト | $-2^{63}$ | $2^{63} - 1$ | 0 |
|
||||
| 浮動小数点 | `float` | 4バイト | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ |
|
||||
| | `double` | 8バイト | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | 0.0 |
|
||||
| 文字 | `char` | 2バイト | 0 | $2^{16} - 1$ | 0 |
|
||||
| ブール | `bool` | 1バイト | $\text{false}$ | $\text{true}$ | $\text{false}$ |
|
||||
|
||||
</div>
|
||||
|
||||
上記の表はJavaの基本データ型に特有であることにご注意ください。すべてのプログラミング言語には独自のデータ型定義があり、占有空間、値の範囲、デフォルト値が異なる場合があります。
|
||||
|
||||
- Pythonでは、整数型`int`は任意のサイズになることができ、利用可能なメモリによってのみ制限されます。浮動小数点`float`は倍精度64ビットです。`char`型は存在せず、単一文字は実際には長さ1の文字列`str`です。
|
||||
- CおよびC++では基本データ型のサイズが指定されておらず、実装とプラットフォームによって異なります。上記の表はLP64[データモデル](https://en.cppreference.com/w/cpp/language/types#Properties)に従っており、LinuxやmacOSを含むUnix 64ビットオペレーティングシステムで使用されています。
|
||||
- CおよびC++における`char`のサイズは1バイトですが、ほとんどのプログラミング言語では、特定の文字エンコーディング方法に依存し、詳細は「文字エンコーディング」の章で説明されています。
|
||||
- ブール値の表現には1ビット(0または1)のみが必要ですが、通常はメモリ内に1バイトとして格納されます。これは、現代のコンピュータCPUが通常1バイトを最小のアドレス可能なメモリ単位として使用するためです。
|
||||
|
||||
では、基本データ型とデータ構造の関係は何でしょうか?データ構造とは、コンピュータ内でデータを組織化し格納する方法であることを知っています。ここでの焦点は「データ」ではなく「構造」です。
|
||||
|
||||
「数値の列」を表現したい場合、自然に配列の使用を考えます。これは、配列の線形構造が数値の隣接性と順序性を表現できるためですが、格納される内容が整数`int`、小数`float`、文字`char`のいずれであっても、「データ構造」とは無関係です。
|
||||
|
||||
言い換えると、**基本データ型はデータの「内容型」を提供し、データ構造はデータの「組織化方法」を提供します**。例えば、以下のコードでは、同じデータ構造(配列)を使用して、`int`、`float`、`char`、`bool`などの異なる基本データ型を格納し表現しています。
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title=""
|
||||
# 様々な基本データ型を使用して配列を初期化
|
||||
numbers: list[int] = [0] * 5
|
||||
decimals: list[float] = [0.0] * 5
|
||||
# Pythonの文字は実際には長さ1の文字列
|
||||
characters: list[str] = ['0'] * 5
|
||||
bools: list[bool] = [False] * 5
|
||||
# Pythonのリストは様々な基本データ型とオブジェクト参照を自由に格納可能
|
||||
data = [0, 0.0, 'a', False, ListNode(0)]
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
int numbers[5];
|
||||
float decimals[5];
|
||||
char characters[5];
|
||||
bool bools[5];
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
int[] numbers = new int[5];
|
||||
float[] decimals = new float[5];
|
||||
char[] characters = new char[5];
|
||||
boolean[] bools = new boolean[5];
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
int[] numbers = new int[5];
|
||||
float[] decimals = new float[5];
|
||||
char[] characters = new char[5];
|
||||
bool[] bools = new bool[5];
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
var numbers = [5]int{}
|
||||
var decimals = [5]float64{}
|
||||
var characters = [5]byte{}
|
||||
var bools = [5]bool{}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
let numbers = Array(repeating: 0, count: 5)
|
||||
let decimals = Array(repeating: 0.0, count: 5)
|
||||
let characters: [Character] = Array(repeating: "a", count: 5)
|
||||
let bools = Array(repeating: false, count: 5)
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title=""
|
||||
// JavaScriptの配列は様々な基本データ型とオブジェクトを自由に格納可能
|
||||
const array = [0, 0.0, 'a', false];
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
const numbers: number[] = [];
|
||||
const characters: string[] = [];
|
||||
const bools: boolean[] = [];
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
List<int> numbers = List.filled(5, 0);
|
||||
List<double> decimals = List.filled(5, 0.0);
|
||||
List<String> characters = List.filled(5, 'a');
|
||||
List<bool> bools = List.filled(5, false);
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
let numbers: Vec<i32> = vec![0; 5];
|
||||
let decimals: Vec<f32> = vec![0.0, 5];
|
||||
let characters: Vec<char> = vec!['0'; 5];
|
||||
let bools: Vec<bool> = vec![false; 5];
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
int numbers[10];
|
||||
float decimals[10];
|
||||
char characters[10];
|
||||
bool bools[10];
|
||||
```
|
||||
|
||||
=== "Kotlin"
|
||||
|
||||
```kotlin title=""
|
||||
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title=""
|
||||
// 様々な基本データ型を使用して配列を初期化
|
||||
var numbers: [5]i32 = undefined;
|
||||
var decimals: [5]f32 = undefined;
|
||||
var characters: [5]u8 = undefined;
|
||||
var bools: [5]bool = undefined;
|
||||
```
|
||||
97
ja/docs/chapter_data_structure/character_encoding.md
Normal file
97
ja/docs/chapter_data_structure/character_encoding.md
Normal file
@@ -0,0 +1,97 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 3.4 文字エンコーディング *
|
||||
|
||||
コンピュータシステムでは、すべてのデータが二進形式で格納され、`char`も例外ではありません。文字を表現するために、各文字と二進数の一対一のマッピングを定義する「文字セット」を開発する必要があります。文字セットがあれば、コンピュータは表を参照して二進数を文字に変換できます。
|
||||
|
||||
## 3.4.1 ASCII文字セット
|
||||
|
||||
<u>ASCIIコード</u>は最も初期の文字セットの一つで、正式にはAmerican Standard Code for Information Interchangeとして知られています。7つの二進桁(1バイトの下位7ビット)を使用して文字を表現し、最大128種類の異なる文字を表現できます。以下の図に示すように、ASCIIには英語の大文字と小文字、0〜9の数字、様々な句読点、特定の制御文字(改行やタブなど)が含まれています。
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> 図 3-6 ASCIIコード </p>
|
||||
|
||||
しかし、**ASCIIは英語の文字のみを表現できます**。コンピュータのグローバル化に伴い、より多くの言語を表現するために<u>EASCII</u>と呼ばれる文字セットが開発されました。ASCIIの7ビット構造から8ビットに拡張し、256文字の表現を可能にしました。
|
||||
|
||||
世界的に、様々な地域固有のEASCII文字セットが導入されました。これらのセットの最初の128文字はASCIIと一致していますが、残りの128文字は異なる言語の要件に対応するために異なって定義されています。
|
||||
|
||||
## 3.4.2 GBK文字セット
|
||||
|
||||
後に、**EASCIIでも多くの言語の文字要件を満たすことができない**ことが判明しました。例えば、中国語には約10万の漢字があり、そのうち数千が定期的に使用されています。1980年、中国標準化委員会は6763の中国語文字を含む<u>GB2312</u>文字セットを発表し、中国語のコンピュータ処理ニーズを本質的に満たしました。
|
||||
|
||||
しかし、GB2312は一部の稀少文字や繁体字を処理できませんでした。<u>GBK</u>文字セットはGB2312を拡張し、21886の中国語文字を含んでいます。GBKエンコーディングスキームでは、ASCII文字は1バイトで表現され、中国語文字は2バイトを使用します。
|
||||
|
||||
## 3.4.3 Unicode文字セット
|
||||
|
||||
コンピュータ技術の急速な発展と多数の文字セットおよびエンコーディング標準により、数多くの問題が発生しました。一方では、これらの文字セットは一般的に特定の言語の文字のみを定義し、多言語環境では適切に機能できませんでした。他方では、同じ言語に対する複数の文字セット標準の存在により、異なるエンコーディング標準を使用するコンピュータ間で情報交換を行う際に文字化けが発生しました。
|
||||
|
||||
当時の研究者たちは考えました:**世界のすべての言語と記号を含む包括的な文字セットが開発されれば、言語横断環境と文字化けに関連する問題を解決できるのではないでしょうか?** このアイデアにインスパイアされて、広範囲な文字セットであるUnicodeが誕生しました。
|
||||
|
||||
<u>Unicode</u>は中国語で「统一码」(統一コード)と呼ばれ、理論的に100万文字以上を収容できます。世界中のすべての文字を単一のセットに組み込み、様々な言語の処理と表示のための汎用文字セットを提供し、異なるエンコーディング標準による文字化けの問題を減らすことを目指しています。
|
||||
|
||||
1991年のリリース以来、Unicodeは新しい言語と文字を含むよう継続的に拡張されています。2022年9月現在、Unicodeには149,186文字が含まれており、様々な言語の文字、記号、さらには絵文字も含まれています。広大なUnicode文字セットでは、一般的に使用される文字は2バイトを占有し、一部の稀少な文字は3バイトまたは4バイトを占有する場合があります。
|
||||
|
||||
Unicodeは各文字に数値(「コードポイント」と呼ばれる)を割り当てる汎用文字セットですが、**これらの文字コードポイントがコンピュータシステムにどのように格納されるべきかは指定していません**。疑問が生じるかもしれません:システムはテキスト内の異なる長さのUnicodeコードポイントをどのように解釈するのでしょうか?例えば、2バイトのコードが与えられた場合、システムはそれが単一の2バイト文字を表すのか、2つの1バイト文字を表すのかをどのように判断するのでしょうか?
|
||||
|
||||
**この問題に対する簡単な解決策は、すべての文字を等長エンコーディングとして格納することです**。以下の図に示すように、「Hello」の各文字は1バイトを占有し、「算法」(アルゴリズム)の各文字は2バイトを占有します。上位ビットをゼロで埋めることで、「Hello 算法」のすべての文字を2バイトとしてエンコードできます。この方法により、システムは2バイトごとに文字を解釈し、フレーズの内容を復元できます。
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> 図 3-7 Unicodeエンコーディング例 </p>
|
||||
|
||||
しかし、ASCIIが示したように、英語のエンコーディングには1バイトのみが必要です。上記のアプローチを使用すると、英語テキストが占有する空間がASCIIエンコーディングと比較して2倍になり、メモリ空間の無駄になります。したがって、より効率的なUnicodeエンコーディング方法が必要です。
|
||||
|
||||
## 3.4.4 UTF-8エンコーディング
|
||||
|
||||
現在、UTF-8は国際的に最も広く使用されているUnicodeエンコーディング方法になっています。**これは可変長エンコーディング**で、文字の複雑さに応じて1〜4バイトを使用して文字を表現します。ASCII文字は1バイトのみが必要で、ラテン文字とギリシャ文字は2バイト、一般的に使用される中国語文字は3バイト、その他の稀少な文字は4バイトが必要です。
|
||||
|
||||
UTF-8のエンコーディング規則は複雑ではなく、2つのケースに分けることができます:
|
||||
|
||||
- 1バイト文字の場合、最上位ビットを$0$に設定し、残りの7ビットをUnicodeコードポイントに設定します。注目すべきは、ASCII文字がUnicodeセットの最初の128コードポイントを占有することです。これは**UTF-8エンコーディングがASCIIと後方互換性がある**ことを意味します。これは、UTF-8を使用して古いASCIIテキストを解析できることを意味します。
|
||||
- 長さ$n$バイトの文字($n > 1$)の場合、最初のバイトの最上位$n$ビットを$1$に設定し、$(n + 1)^{\text{th}}$ビットを$0$に設定します。2番目のバイトから、各バイトの最上位2ビットを$10$に設定します。残りのビットはUnicodeコードポイントを埋めるために使用されます。
|
||||
|
||||
以下の図は「Hello算法」のUTF-8エンコーディングを示しています。最上位$n$ビットが$1$に設定されているため、システムは最上位ビットで$1$に設定されたビット数を数えることで文字の長さを$n$として決定できることが観察できます。
|
||||
|
||||
しかし、なぜ残りのバイトの最上位2ビットを$10$に設定するのでしょうか?実際、この$10$は一種のチェックサムとして機能します。システムが間違ったバイトからテキストの解析を開始した場合、バイトの先頭の$10$によりシステムは異常を迅速に検出できます。
|
||||
|
||||
$10$をチェックサムとして使用する理由は、UTF-8エンコーディング規則の下では、文字の最上位2ビットが$10$になることは不可能だからです。これは矛盾により証明できます:文字の最上位2ビットが$10$の場合、文字の長さが$1$であることを示し、これはASCIIに対応します。しかし、ASCII文字の最上位ビットは$0$であるべきで、これは仮定と矛盾します。
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> 図 3-8 UTF-8エンコーディング例 </p>
|
||||
|
||||
UTF-8以外にも、他の一般的なエンコーディング方法には以下があります:
|
||||
|
||||
- **UTF-16エンコーディング**:2または4バイトを使用して文字を表現します。すべてのASCII文字と一般的に使用される非英語文字は2バイトで表現され、少数の文字は4バイトが必要です。2バイト文字の場合、UTF-16エンコーディングはUnicodeコードポイントと等しくなります。
|
||||
- **UTF-32エンコーディング**:すべての文字が4バイトを使用します。これは、UTF-32がUTF-8やUTF-16よりも多くの空間を占有することを意味し、特にASCII文字の割合が高いテキストでは顕著です。
|
||||
|
||||
ストレージ空間の観点から、UTF-8を使用して英語文字を表現することは1バイトのみが必要なため非常に効率的です。UTF-16を使用して一部の非英語文字(中国語など)をエンコードすることは、2バイトのみが必要なためより効率的になる場合があります。一方、UTF-8では3バイトが必要になる場合があります。
|
||||
|
||||
互換性の観点から、UTF-8は最も汎用性があり、多くのツールとライブラリがUTF-8を優先的にサポートしています。
|
||||
|
||||
## 3.4.5 プログラミング言語における文字エンコーディング
|
||||
|
||||
歴史的に、多くのプログラミング言語はプログラム実行中の文字列処理にUTF-16やUTF-32などの固定長エンコーディングを利用していました。これにより文字列を配列として処理でき、いくつかの利点があります:
|
||||
|
||||
- **ランダムアクセス**:UTF-16でエンコードされた文字列は簡単にランダムアクセスできます。可変長エンコーディングであるUTF-8の場合、$i^{th}$文字の位置を特定するには文字列の開始から$i^{th}$位置まで走査する必要があり、$O(n)$時間がかかります。
|
||||
- **文字数カウント**:ランダムアクセスと同様に、UTF-16でエンコードされた文字列の文字数をカウントすることは$O(1)$操作です。しかし、UTF-8でエンコードされた文字列の文字数をカウントするには文字列全体を走査する必要があります。
|
||||
- **文字列操作**:分割、連結、挿入、削除などの多くの文字列操作は、UTF-16でエンコードされた文字列で簡単です。これらの操作は一般的に、UTF-8エンコーディングの有効性を確保するためにUTF-8でエンコードされた文字列で追加の計算が必要です。
|
||||
|
||||
プログラミング言語における文字エンコーディングスキームの設計は、様々な要因を含む興味深いトピックです:
|
||||
|
||||
- Javaの`String`型はUTF-16エンコーディングを使用し、各文字が2バイトを占有します。これは、16ビットがすべての可能な文字を表現するのに十分であるという初期の信念に基づいており、後に間違いであることが証明されました。Unicode標準が16ビットを超えて拡張されると、Javaの文字は「サロゲートペア」として知られる16ビット値のペアで表現される場合があります。
|
||||
- JavaScriptとTypeScriptは、Javaと同様の理由でUTF-16エンコーディングを使用します。JavaScriptが1995年にNetscapeによって最初に導入されたとき、Unicodeはまだ初期段階にあり、16ビットエンコーディングはすべてのUnicode文字を表現するのに十分でした。
|
||||
- C#はUTF-16エンコーディングを使用し、これは主にMicrosoftによって設計された.NETプラットフォーム、および多くのMicrosoft技術(Windowsオペレーティングシステムを含む)がUTF-16エンコーディングを広範囲に使用しているためです。
|
||||
|
||||
文字数の過小評価により、これらの言語は16ビットを超えるUnicode文字を表現するために「サロゲートペア」を使用する必要がありました。このアプローチには欠点があります:サロゲートペアを含む文字列は2バイトまたは4バイトを占有する文字を持つ場合があり、固定長エンコーディングの利点を失います。さらに、サロゲートペアの処理はプログラミングに複雑さとデバッグの困難さを追加します。
|
||||
|
||||
これらの課題に対処するため、一部の言語は代替エンコーディング戦略を採用しています:
|
||||
|
||||
- Pythonの`str`型は、文字のストレージ長が文字列内の最大のUnicodeコードポイントに依存する柔軟な表現でUnicodeエンコーディングを使用します。すべての文字がASCIIの場合、各文字は1バイトを占有し、基本多言語面(BMP)内の文字は2バイト、BMPを超える文字は4バイトを占有します。
|
||||
- Goの`string`型は内部的にUTF-8エンコーディングを使用します。Goは個別のUnicodeコードポイントを表現するための`rune`型も提供します。
|
||||
- Rustの`str`と`String`型は内部的にUTF-8エンコーディングを使用します。Rustは個別のUnicodeコードポイント用の`char`型も提供します。
|
||||
|
||||
上記の議論は、プログラミング言語での文字列の格納方法に関するものであり、**ファイルでの文字列の格納方法やネットワーク上での送信方法とは異なる**ことに注意することが重要です。ファイルストレージやネットワーク送信では、文字列は通常、最適な互換性と空間効率のためにUTF-8形式でエンコードされます。
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 3.1 データ構造の分類
|
||||
|
||||
一般的なデータ構造には、配列、連結リスト、スタック、キュー、ハッシュ表、木、ヒープ、グラフがあります。これらは「論理構造」と「物理構造」に分類できます。
|
||||
|
||||
## 3.1.1 論理構造:線形と非線形
|
||||
|
||||
**論理構造はデータ要素間の論理的関係を明らかにします**。配列と連結リストでは、データは特定の順序で配置され、データ間の線形関係を示しています。一方、木では、データは上から下へ階層的に配置され、「祖先」と「子孫」間の派生関係を示しています。そして、グラフはノードとエッジから構成され、複雑なネットワーク関係を反映しています。
|
||||
|
||||
下図に示されているように、論理構造は「線形」と「非線形」の2つの主要カテゴリに分けることができます。線形構造はより直感的で、データが論理関係において線形に配置されていることを示しています。非線形構造は、逆に非線形に配置されています。
|
||||
|
||||
- **線形データ構造**:配列、連結リスト、スタック、キュー、ハッシュ表。要素が一対一の順次関係を持ちます。
|
||||
- **非線形データ構造**:木、ヒープ、グラフ、ハッシュ表。
|
||||
|
||||
非線形データ構造は、さらに木構造とネットワーク構造に分けることができます。
|
||||
|
||||
- **木構造**:木、ヒープ、ハッシュ表。要素が一対多の関係を持ちます。
|
||||
- **ネットワーク構造**:グラフ。要素が多対多の関係を持ちます。
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> 図 3-1 Linear and non-linear data structures </p>
|
||||
|
||||
## 3.1.2 物理構造:連続と分散
|
||||
|
||||
**アルゴリズムの実行中、処理されるデータはメモリに格納されます**。下図はコンピュータのメモリスティックを示しており、各黒い正方形は物理メモリ空間です。メモリを巨大なExcelスプレッドシートと考えることができ、各セルは一定量のデータを格納できます。
|
||||
|
||||
**システムはメモリアドレスによって目標位置のデータにアクセスします**。下図に示されているように、コンピュータは特定のルールに従って表の各セルに一意の識別子を割り当て、各メモリ空間が一意のメモリアドレスを持つことを保証します。これらのアドレスにより、プログラムはメモリに格納されたデータにアクセスできます。
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> 図 3-2 Memory stick, memory spaces, memory addresses </p>
|
||||
|
||||
!!! tip
|
||||
|
||||
メモリをExcelスプレッドシートに比較することは簡略化された類推であることに注意してください。メモリの実際の動作メカニズムはより複雑で、アドレス空間、メモリ管理、キャッシュメカニズム、仮想メモリ、物理メモリなどの概念が関係しています。
|
||||
|
||||
メモリはすべてのプログラムの共有リソースです。あるメモリブロックが1つのプログラムによって占有されると、他のプログラムが同時に使用することはできません。**したがって、メモリリソースはデータ構造とアルゴリズムの設計における重要な考慮事項です**。例えば、アルゴリズムのピークメモリ使用量は、システムの残り空きメモリを超えてはいけません。連続したメモリブロックが不足している場合は、非連続メモリブロックに格納できるデータ構造を選択する必要があります。
|
||||
|
||||
下図に示されているように、**物理構造はコンピュータメモリにおけるデータの格納方法を反映し**、連続空間格納(配列)と非連続空間格納(連結リスト)に分けることができます。2つのタイプの物理構造は、時間効率と空間効率の観点で補完的な特性を示します。
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> 図 3-3 Contiguous space storage and dispersed space storage </p>
|
||||
|
||||
**すべてのデータ構造は配列、連結リスト、またはその組み合わせに基づいて実装されていることに注意してください**。例えば、スタックとキューは配列または連結リストのどちらでも実装できます。ハッシュ表の実装には配列と連結リストの両方が関係する場合があります。
|
||||
|
||||
- **配列ベースの実装**:スタック、キュー、ハッシュ表、木、ヒープ、グラフ、行列、テンソル(次元$\geq 3$の配列)。
|
||||
- **連結リストベースの実装**:スタック、キュー、ハッシュ表、木、ヒープ、グラフなど。
|
||||
|
||||
配列に基づいて実装されたデータ構造は「静的データ構造」とも呼ばれ、初期化後に長さを変更できないことを意味します。逆に、連結リストに基づいたものは「動的データ構造」と呼ばれ、プログラム実行中にサイズを調整できます。
|
||||
|
||||
!!! tip
|
||||
|
||||
物理構造を理解するのが困難な場合は、次の章「配列と連結リスト」を読んでから、この節に戻ることをお勧めします。
|
||||
22
ja/docs/chapter_data_structure/index.md
Normal file
22
ja/docs/chapter_data_structure/index.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
comments: true
|
||||
icon: material/shape-outline
|
||||
---
|
||||
|
||||
# 第 3 章 データ構造
|
||||
|
||||
{ class="cover-image" }
|
||||
|
||||
!!! abstract
|
||||
|
||||
データ構造は堅牢で多様なフレームワークとして機能します。
|
||||
|
||||
データの整然とした組織化のための設計図を提供し、その上でアルゴリズムが生き生きと動き出します。
|
||||
|
||||
## 章の内容
|
||||
|
||||
- [3.1 データ構造の分類](classification_of_data_structure.md)
|
||||
- [3.2 基本データ型](basic_data_types.md)
|
||||
- [3.3 数値エンコーディング *](number_encoding.md)
|
||||
- [3.4 文字エンコーディング *](character_encoding.md)
|
||||
- [3.5 まとめ](summary.md)
|
||||
162
ja/docs/chapter_data_structure/number_encoding.md
Normal file
162
ja/docs/chapter_data_structure/number_encoding.md
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 3.3 数値エンコーディング *
|
||||
|
||||
!!! tip
|
||||
|
||||
本書では、アスタリスク「*」が付いた章は任意読書です。時間が不足している場合や難しいと感じる場合は、最初はこれらをスキップして、必須の章を完了した後に戻ることができます。
|
||||
|
||||
## 3.3.1 整数エンコーディング
|
||||
|
||||
前の節の表で、すべての整数型は正の数よりも1つ多い負の数を表現できることを観察しました。例えば、`byte`の範囲は$[-128, 127]$です。この現象は直感に反するように見え、その根本的な理由には符号絶対値、1の補数、2の補数エンコーディングの知識が関与しています。
|
||||
|
||||
まず重要なことは、**数値はコンピュータ内で2の補数形式で格納される**ということです。なぜそうなのかを分析する前に、これら3つのエンコーディング方法を定義しましょう:
|
||||
|
||||
- **符号絶対値**:数値の二進表現の最上位ビットを符号ビットとし、$0$は正の数、$1$は負の数を表します。残りのビットは数値の値を表します。
|
||||
- **1の補数**:正の数の1の補数は符号絶対値と同じです。負の数の場合、符号ビット以外のすべてのビットを反転して得られます。
|
||||
- **2の補数**:正の数の2の補数は符号絶対値と同じです。負の数の場合、その1の補数に$1$を加えて得られます。
|
||||
|
||||
以下の図は、符号絶対値、1の補数、2の補数間の変換を示しています:
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> 図 3-4 符号絶対値、1の補数、2の補数間の変換 </p>
|
||||
|
||||
<u>符号絶対値</u>は最も直感的ですが、制限があります。一つには、**符号絶対値の負の数は計算で直接使用できません**。例えば、符号絶対値で$1 + (-2)$を計算すると$-3$になり、これは正しくありません。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
& 1 + (-2) \newline
|
||||
& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline
|
||||
& = 1000 \; 0011 \newline
|
||||
& \rightarrow -3
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
この問題に対処するため、コンピュータは<u>1の補数</u>を導入しました。1の補数に変換して$1 + (-2)$を計算し、結果を符号絶対値に戻すと、正しい結果$-1$が得られます。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
& 1 + (-2) \newline
|
||||
& \rightarrow 0000 \; 0001 \; \text{(符号絶対値)} + 1000 \; 0010 \; \text{(符号絶対値)} \newline
|
||||
& = 0000 \; 0001 \; \text{(1の補数)} + 1111 \; 1101 \; \text{(1の補数)} \newline
|
||||
& = 1111 \; 1110 \; \text{(1の補数)} \newline
|
||||
& = 1000 \; 0001 \; \text{(符号絶対値)} \newline
|
||||
& \rightarrow -1
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
また、**符号絶対値では0に2つの表現があります**:$+0$と$-0$です。これは0に対して2つの異なる二進エンコーディングがあることを意味し、曖昧さを引き起こす可能性があります。例えば、条件チェックで正と負の0を区別しないと、正しくない結果になる可能性があります。この曖昧さに対処するには追加のチェックが必要で、計算効率が低下する可能性があります。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
+0 & \rightarrow 0000 \; 0000 \newline
|
||||
-0 & \rightarrow 1000 \; 0000
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
符号絶対値と同様に、1の補数も正と負の0の曖昧さに悩まされます。そのため、コンピュータはさらに<u>2の補数</u>を導入しました。符号絶対値、1の補数、2の補数における負の0の変換過程を観察してみましょう:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
-0 \rightarrow \; & 1000 \; 0000 \; \text{(符号絶対値)} \newline
|
||||
= \; & 1111 \; 1111 \; \text{(1の補数)} \newline
|
||||
= 1 \; & 0000 \; 0000 \; \text{(2の補数)} \newline
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
負の0の1の補数に$1$を加えると桁上がりが発生しますが、`byte`の長さは8ビットのみのため、9番目のビットへの桁上がり$1$は破棄されます。したがって、**負の0の2の補数は$0000 \; 0000$**で、正の0と同じになり、曖昧さが解決されます。
|
||||
|
||||
最後の謎は、`byte`の$[-128, 127]$の範囲で、追加の負の数$-128$があることです。$[-127, +127]$の区間では、すべての整数に対応する符号絶対値、1の補数、2の補数があり、相互変換が可能であることを観察します。
|
||||
|
||||
しかし、**2の補数$1000 \; 0000$は対応する符号絶対値を持たない例外です**。変換方法によると、その符号絶対値は$0000 \; 0000$で、0を示します。これは矛盾を示しています。なぜなら、その2の補数は自分自身を表すべきだからです。コンピュータは、この特別な2の補数$1000 \; 0000$を$-128$を表すものとして指定しています。実際、2の補数での$(-1) + (-127)$の計算結果は$-128$になります。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
& (-127) + (-1) \newline
|
||||
& \rightarrow 1111 \; 1111 \; \text{(符号絶対値)} + 1000 \; 0001 \; \text{(符号絶対値)} \newline
|
||||
& = 1000 \; 0000 \; \text{(1の補数)} + 1111 \; 1110 \; \text{(1の補数)} \newline
|
||||
& = 1000 \; 0001 \; \text{(2の補数)} + 1111 \; 1111 \; \text{(2の補数)} \newline
|
||||
& = 1000 \; 0000 \; \text{(2の補数)} \newline
|
||||
& \rightarrow -128
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
お気づきかもしれませんが、これらの計算はすべて加算であり、重要な事実を示唆しています:**コンピュータの内部ハードウェア回路は主に加算演算を中心に設計されています**。これは、加算が乗算、除算、減算などの他の演算と比較してハードウェアで実装しやすく、並列化が容易で高速計算が可能だからです。
|
||||
|
||||
これはコンピュータが加算のみを実行できることを意味するものではありません。**加算と基本的な論理演算を組み合わせることで、コンピュータは様々な他の数学演算を実行できます**。例えば、減算$a - b$は$a + (-b)$に変換でき、乗算と除算は複数の加算または減算に変換できます。
|
||||
|
||||
コンピュータで2の補数を使用する理由をまとめることができます:2の補数表現により、コンピュータは同じ回路と演算を使用して正と負の数の加算を処理でき、減算用の特別なハードウェア回路の必要性を排除し、正と負の0の曖昧さを回避できます。これによりハードウェア設計が大幅に簡素化され、計算効率が向上します。
|
||||
|
||||
2の補数の設計は非常に巧妙で、スペースの制約により、ここで停止します。興味のある読者はさらに探求することを奨励します。
|
||||
|
||||
## 3.3.2 浮動小数点数エンコーディング
|
||||
|
||||
興味深いことに気づいたかもしれません:同じ4バイトの長さにもかかわらず、なぜ`float`は`int`と比較してはるかに大きい値の範囲を持つのでしょうか?これは直感に反するように見えます。`float`は分数を表現する必要があるため、範囲が縮小すると予想されるからです。
|
||||
|
||||
実際、**これは浮動小数点数(`float`)で使用される異なる表現方法によるものです**。32ビットの二進数を次のように考えてみましょう:
|
||||
|
||||
$$
|
||||
b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0
|
||||
$$
|
||||
|
||||
IEEE 754標準によると、32ビットの`float`は次の3つの部分で構成されます:
|
||||
|
||||
- 符号ビット$\mathrm{S}$:1ビットを占有し、$b_{31}$に対応します。
|
||||
- 指数ビット$\mathrm{E}$:8ビットを占有し、$b_{30} b_{29} \ldots b_{23}$に対応します。
|
||||
- 仮数ビット$\mathrm{N}$:23ビットを占有し、$b_{22} b_{21} \ldots b_0$に対応します。
|
||||
|
||||
二進`float`数の値は次のように計算されます:
|
||||
|
||||
$$
|
||||
\text{val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2 - 127} \times \left(1 . b_{22} b_{21} \ldots b_0\right)_2
|
||||
$$
|
||||
|
||||
十進公式に変換すると、次のようになります:
|
||||
|
||||
$$
|
||||
\text{val} = (-1)^{\mathrm{S}} \times 2^{\mathrm{E} - 127} \times (1 + \mathrm{N})
|
||||
$$
|
||||
|
||||
各成分の範囲は:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline
|
||||
(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} \times 2^{-i}) \subset [1, 2 - 2^{-23}]
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> 図 3-5 IEEE 754標準での浮動小数点数の計算例 </p>
|
||||
|
||||
上の図を観察すると、例のデータ$\mathrm{S} = 0$、$\mathrm{E} = 124$、$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$が与えられた場合:
|
||||
|
||||
$$
|
||||
\text{val} = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
|
||||
$$
|
||||
|
||||
これで最初の質問に答えることができます:**`float`の表現には指数ビットが含まれているため、`int`よりもはるかに大きい範囲を持ちます**。上記の計算に基づくと、`float`で表現可能な最大正の数は約$2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$で、最小負の数は符号ビットを切り替えることで得られます。
|
||||
|
||||
**しかし、`float`の拡張された範囲のトレードオフは精度の犠牲です**。整数型`int`は32ビットすべてを数値表現に使用し、値は均等に分布していますが、指数ビットのため、`float`の値が大きいほど、隣接する数値間の差が大きくなります。
|
||||
|
||||
以下の表に示すように、指数ビット$\mathrm{E} = 0$と$\mathrm{E} = 255$は特別な意味を持ち、**0、無限大、$\mathrm{NaN}$などを表現するために使用されます**。
|
||||
|
||||
<p align="center"> 表 3-2 指数ビットの意味 </p>
|
||||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
| 指数ビットE | 仮数ビット$\mathrm{N} = 0$ | 仮数ビット$\mathrm{N} \ne 0$ | 計算公式 |
|
||||
| ------------------ | ----------------------------- | ------------------------------- | ---------------------------------------------------------------------- |
|
||||
| $0$ | $\pm 0$ | 非正規化数 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ |
|
||||
| $1, 2, \dots, 254$ | 正規化数 | 正規化数 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ |
|
||||
| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | |
|
||||
|
||||
</div>
|
||||
|
||||
非正規化数は浮動小数点数の精度を大幅に向上させることは注目に値します。最小の正の正規化数は$2^{-126}$で、最小の正の非正規化数は$2^{-126} \times 2^{-23}$です。
|
||||
|
||||
倍精度`double`も`float`と同様の表現方法を使用しますが、簡潔さのためここでは詳述しません。
|
||||
70
ja/docs/chapter_data_structure/summary.md
Normal file
70
ja/docs/chapter_data_structure/summary.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 3.5 まとめ
|
||||
|
||||
### 1. 重要なポイント
|
||||
|
||||
- データ構造は論理構造と物理構造の2つの観点から分類できます。論理構造はデータ間の論理的関係を記述し、物理構造はデータがメモリにどのように格納されるかを記述します。
|
||||
- よく使用される論理構造には、線形構造、木、ネットワークがあります。通常、論理構造に基づいてデータ構造を線形(配列、連結リスト、スタック、キュー)と非線形(木、グラフ、ヒープ)に分けます。ハッシュ表の実装は線形と非線形の両方のデータ構造を含む場合があります。
|
||||
- プログラムが実行中の際、データはメモリに格納されます。各メモリ空間には対応するアドレスがあり、プログラムはこれらのアドレスを通じてデータにアクセスします。
|
||||
- 物理構造は連続空間格納(配列)と離散空間格納(連結リスト)に分けることができます。すべてのデータ構造は配列、連結リスト、またはその両方の組み合わせを使用して実装されます。
|
||||
- コンピュータの基本データ型には、整数(`byte`、`short`、`int`、`long`)、浮動小数点数(`float`、`double`)、文字(`char`)、ブール値(`bool`)が含まれます。データ型の値の範囲は、そのサイズと表現に依存します。
|
||||
- 符号絶対値、1の補数、2の補数は、コンピュータで整数をエンコードする3つの方法であり、相互に変換することができます。符号絶対値の最上位ビットは符号ビットで、残りのビットは数値の値を表します。
|
||||
- 整数はコンピュータで2の補数によってエンコードされます。この表現の利点には、(i)コンピュータが正と負の整数の加算を統一できる、(ii)減算用の特別なハードウェア回路を設計する必要がない、(iii)正と負の0の曖昧さがない、があります。
|
||||
- 浮動小数点数のエンコーディングは、1つの符号ビット、8つの指数ビット、23の仮数ビットで構成されます。指数ビットのため、浮動小数点数の範囲は整数よりもはるかに大きくなりますが、精度を犠牲にします。
|
||||
- ASCIIは最初期の英語文字セットで、1バイトの長さで計127文字です。GBKは人気のある中国語文字セットで、2万文字以上の中国語文字を含みます。Unicodeは世界の様々な言語の文字を含む完全な文字セット標準を提供することを目的とし、文字エンコーディング方法の不一致による文字化け問題を解決します。
|
||||
- UTF-8は最も人気があり一般的なUnicodeエンコーディング方法です。これは可変長エンコーディング方法で、優れた拡張性と空間効率を持ちます。UTF-16とUTF-32は固定長エンコーディング方法です。中国語文字をエンコードする際、UTF-16はUTF-8よりも少ない空間を使用します。JavaやC#などのプログラミング言語はデフォルトでUTF-16エンコーディングを使用します。
|
||||
|
||||
### 2. Q & A
|
||||
|
||||
**Q**: なぜハッシュ表は線形と非線形の両方のデータ構造を含むのですか?
|
||||
|
||||
ハッシュ表の基礎構造は配列です。ハッシュ衝突を解決するために、「チェイン法」を使用する場合があります(後の節「ハッシュ衝突」で説明):配列の各バケットは連結リストを指し、その長さが特定の閾値より大きくなると木(通常は赤黒木)に変換される可能性があります。
|
||||
格納の観点から、ハッシュ表の基礎構造は配列で、各バケットには値、連結リスト、または木が含まれる場合があります。したがって、ハッシュ表は線形データ構造(配列、連結リスト)と非線形データ構造(木)の両方を含む場合があります。
|
||||
|
||||
**Q**: `char`型の長さは1バイトですか?
|
||||
|
||||
`char`型の長さは、プログラミング言語のエンコーディング方法によって決まります。例えば、Java、JavaScript、TypeScript、C#はすべてUTF-16エンコーディング(Unicodeコードポイントを保存するため)を使用するため、`char`型の長さは2バイトです。
|
||||
|
||||
**Q**: 配列ベースのデータ構造を「静的データ構造」と呼ぶことに曖昧さはありませんか?スタックもプッシュやポップなどの「動的」操作を実行できます。
|
||||
|
||||
スタックは動的なデータ操作を実装できますが、データ構造は依然として「静的」です(長さが固定)。配列ベースのデータ構造は動的に要素を追加または削除できますが、その容量は固定されています。スタックサイズが事前に割り当てられたサイズを超える場合、古い配列は新しく作成されたより大きな配列にコピーされます。
|
||||
|
||||
**Q**: スタック(キュー)を構築する際、そのサイズが指定されていないのに、なぜ「静的データ構造」なのですか?
|
||||
|
||||
高級プログラミング言語では、スタック(キュー)の初期容量を手動で指定する必要はありません。このタスクはクラス内で自動的に完了されます。例えば、Javaの`ArrayList`の初期容量は通常10です。さらに、拡張操作も自動的に完了されます。詳細については、後続の「リスト」の章を参照してください。
|
||||
|
||||
**Q**: 符号絶対値を2の補数に変換する方法は「最初に否定してから1を加える」ですので、2の補数を符号絶対値に変換することはその逆操作「最初に1を減算してから否定する」であるべきです。
|
||||
しかし、2の補数も「最初に否定してから1を加える」を通じて符号絶対値に変換できます。なぜですか?
|
||||
|
||||
**A**: これは、符号絶対値と2の補数間の相互変換が「補数」の計算と等価だからです。まず補数を定義します:$a + b = c$と仮定すると、$a$は$b$の$c$に対する補数と言い、逆に$b$は$a$の$c$に対する補数と言います。
|
||||
|
||||
長さ$n = 4$の二進数$0010$が与えられた場合、この数が符号絶対値(符号ビットを無視)の場合、その2の補数は「最初に否定してから1を加える」ことで得られます:
|
||||
|
||||
$$
|
||||
0010 \rightarrow 1101 \rightarrow 1110
|
||||
$$
|
||||
|
||||
符号絶対値と2の補数の和が$0010 + 1110 = 10000$であることを観察します。つまり、2の補数$1110$は符号絶対値$0010$の$10000$に対する「補数」です。**これは、上記の「最初に否定してから1を加える」が$10000$に対する補数の計算と等価であることを意味します**。
|
||||
|
||||
では、$1110$の$10000$に対する「補数」は何でしょうか?「最初に否定してから1を加える」ことで計算できます:
|
||||
|
||||
$$
|
||||
1110 \rightarrow 0001 \rightarrow 0010
|
||||
$$
|
||||
|
||||
言い換えると、符号絶対値と2の補数は互いに$10000$に対する「補数」であるため、「符号絶対値から2の補数」と「2の補数から符号絶対値」は同じ操作(最初に否定してから1を加える)で実装できます。
|
||||
|
||||
もちろん、「最初に否定してから1を加える」の逆操作を使用して2の補数$1110$の符号絶対値を求めることもできます。つまり、「最初に1を減算してから否定する」:
|
||||
|
||||
$$
|
||||
1110 \rightarrow 1101 \rightarrow 0010
|
||||
$$
|
||||
|
||||
要約すると、「最初に否定してから1を加える」と「最初に1を減算してから否定する」は両方とも$10000$に対する補数を計算しており、等価です。
|
||||
|
||||
本質的に、「否定」操作は実際には$1111$に対する補数を求めることです(`符号絶対値 + 1の補数 = 1111`が常に成り立つため)。そして1の補数に1を加えることは$10000$に対する2の補数と等しくなります。
|
||||
|
||||
上記では$n = 4$を例に取りましたが、任意の桁数の任意の二進数に一般化できます。
|
||||
Reference in New Issue
Block a user