* Retranslate the complete Japanese online edition from Chinese * Fit Japanese homepage labels and localize heading permalinks * Point Japanese contribution instructions to the Japanese preview * Replace locale conditionals with multilingual UI tables
6.8 KiB
まとめ
要点の振り返り
- データ構造は論理構造と物理構造の二つの観点で分類できます。論理構造は要素間の関係、物理構造はメモリ内での格納方法を表します。
- 主な論理構造には、線形、木、ネットワークがあります。通常は線形(配列、連結リスト、スタック、キュー)と非線形(木、グラフ、ヒープ)に分けます。ハッシュテーブルの実装には、線形と非線形の両方が含まれる場合があります。
- プログラムの実行時、データはメモリに保存されます。各領域にはメモリアドレスがあり、プログラムはそれを使ってデータにアクセスします。
- 物理構造は主に、連続した領域への格納(配列)と分散した領域への格納(連結リスト)に分けられます。すべてのデータ構造は、この二つまたはその組み合わせで実装されます。
- 基本データ型には、整数の
byte、short、int、long、浮動小数点のfloat、double、文字のchar、ブールのboolがあります。値の範囲は、使用する空間と表現方法で決まります。 - 符号絶対値表現、1 の補数表現、2 の補数表現は、数値を符号化する三つの方法で、相互に変換できます。整数の符号絶対値表現では、最上位ビットが符号、残りが数の値を表します。
- 整数はコンピュータ内で 2 の補数表現で保存されます。正負の加算を同じ方法で処理でき、減算専用のハードウェア回路が不要になり、正負のゼロという曖昧さもなくなります。
- 浮動小数点数の符号化は、符号 1 ビット、指数 8 ビット、仮数 23 ビットで構成されます。指数部があるため整数よりはるかに広い範囲を表せますが、精度を犠牲にします。
- ASCII は初期の英語用文字集合で、長さは 1 バイト、128 文字を収録しています。GBK はよく使われる中国語の文字集合で、2 万字以上の漢字を収録しています。Unicode は、世界の各言語の文字を収めた完全な文字集合の標準を目指し、符号化方式の違いによる文字化けの解消を図っています。
- UTF-8 は最も普及した Unicode の符号化方式で、汎用性に優れています。可変長で拡張性が高く、記憶領域を効率よく使えます。UTF-16 と UTF-32 は固定長の符号化方式です。中国語では UTF-16 の方が UTF-8 より少ない空間で済みます。Java や C# などは標準で UTF-16 を使います。
Q & A
Q:ハッシュテーブルに、線形と非線形のデータ構造の両方が含まれるのはなぜですか。
ハッシュテーブルの基盤は配列ですが、衝突の解決には「チェイン法」(後の「ハッシュ衝突」の節で説明します)を使うことがあります。各バケットが連結リストを指し、その長さがしきい値を超えると、木(通常は赤黒木)に変換する場合があります。
格納の観点では、基盤となる配列の各バケットには、一つの値、連結リスト、木のいずれかが入る可能性があります。そのため、線形の構造(配列、連結リスト)と非線形の構造(木)が同時に含まれる場合があります。
Q:char 型の長さは 1 バイトですか。
char の長さは、言語が使う符号化方式で決まります。たとえば Java、JavaScript、TypeScript、C# は UTF-16(Unicode コードポイントを保存する方式)を使うため、char の長さは 2 バイトです。
Q:配列で実装したデータ構造を「静的」と呼ぶのは曖昧ではありませんか。スタックのプッシュやポップは「動的」な操作です。
スタックのデータ操作は確かに動的ですが、構造自体は「静的」(長さが固定)です。配列を使った構造で要素を動的に追加・削除できても、容量は固定されています。確保済みの容量を超えたら、より大きな配列を作り、元の内容をコピーする必要があります。
Q:スタックやキューを作るときに大きさを指定していないのに、なぜ「静的データ構造」なのですか。
高水準言語では、初期容量の設定をクラスの内部で自動的に行うため、自分で指定する必要はありません。たとえば Java の ArrayList の初期容量は通常 10 です。容量拡張も自動で行われます。詳しくは後の「リスト」の節で説明します。
Q:符号絶対値表現から 2 の補数表現への変換は「反転してから 1 を足す」ので、逆変換は「1 を引いてから反転する」はずです。逆変換にも「反転してから 1 を足す」が使えるのはなぜですか。
相互変換は、実際には「補数」を求める操作だからです。まず補数を定義します。a + b = c なら、a は b の c に対する補数であり、逆に b も a の c に対する補数です。
n = 4 ビットの二進数 0010 を、符号ビットを考慮せず符号絶対値表現とみなすと、対応する 2 の補数は「反転してから 1 を足す」ことで得られます。
0010 \rightarrow 1101 \rightarrow 1110
元の値と補数の和は 0010 + 1110 = 10000 です。つまり、補数 1110 は、元の値 0010 の 10000 に対する「補数」です。「反転してから 1 を足す」は、実際には 10000 に対する補数を求める操作なのです。
では、1110 の 10000 に対する「補数」は何でしょうか。同じく「反転してから 1 を足す」で求められます。
1110 \rightarrow 0001 \rightarrow 0010
つまり、元の表現と 2 の補数表現は、互いに 10000 に対する「補数」なので、どちらへの変換も同じ操作(反転してから 1 を足す)で行えます。
もちろん、1110 から元の表現を求めるには、「1 を引いてから反転する」という逆操作も使えます。
1110 \rightarrow 1101 \rightarrow 0010
「反転してから 1 を足す」と「1 を引いてから反転する」は、どちらも 10000 に対する補数を求める操作であり、同等なのです。
本質的には、「反転」は 1111 に対する補数を求める操作です(常に 元の値 + 1 の補数 = 1111 が成り立ちます)。さらに 1 を足して得る 2 の補数は、10000 に対する補数になります。
ここでは n = 4 を例にしましたが、任意のビット数の二進数に一般化できます。