# まとめ ### 要点の振り返り - データ構造は論理構造と物理構造の二つの観点で分類できます。論理構造は要素間の関係、物理構造はメモリ内での格納方法を表します。 - 主な論理構造には、線形、木、ネットワークがあります。通常は線形(配列、連結リスト、スタック、キュー)と非線形(木、グラフ、ヒープ)に分けます。ハッシュテーブルの実装には、線形と非線形の両方が含まれる場合があります。 - プログラムの実行時、データはメモリに保存されます。各領域にはメモリアドレスがあり、プログラムはそれを使ってデータにアクセスします。 - 物理構造は主に、連続した領域への格納(配列)と分散した領域への格納(連結リスト)に分けられます。すべてのデータ構造は、この二つまたはその組み合わせで実装されます。 - 基本データ型には、整数の `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$ を例にしましたが、任意のビット数の二進数に一般化できます。