# 小結

### 重點回顧

- 資料結構可以從邏輯結構和物理結構兩個角度進行分類。邏輯結構描述了資料元素之間的邏輯關係，而物理結構描述了資料在計算機記憶體中的儲存方式。
- 常見的邏輯結構包括線性、樹狀和網狀等。通常我們根據邏輯結構將資料結構分為線性（陣列、鏈結串列、堆疊、佇列）和非線性（樹、圖、堆積）兩種。雜湊表的實現可能同時包含線性資料結構和非線性資料結構。
- 當程式執行時，資料被儲存在計算機記憶體中。每個記憶體空間都擁有對應的記憶體位址，程式透過這些記憶體位址訪問資料。
- 物理結構主要分為連續空間儲存（陣列）和分散空間儲存（鏈結串列）。所有資料結構都是由陣列、鏈結串列或兩者的組合實現的。
- 計算機中的基本資料型別包括整數 `byte`、`short`、`int`、`long` ，浮點數 `float`、`double` ，字元 `char` 和布林 `bool` 。它們的取值範圍取決於佔用空間大小和表示方式。
- 原碼、一補數和二補數是在計算機中編碼數字的三種方法，它們之間可以相互轉換。整數的原碼的最高位是符號位，其餘位是數字的值。
- 整數在計算機中是以二補數的形式儲存的。在二補數表示下，計算機可以對正數和負數的加法一視同仁，不需要為減法操作單獨設計特殊的硬體電路，並且不存在正負零歧義的問題。
- 浮點數的編碼由 1 位符號位、8 位指數位和 23 位分數位構成。由於存在指數位，因此浮點數的取值範圍遠大於整數，代價是犧牲了精度。
- ASCII 碼是最早出現的英文字符集，長度為 1 位元組，共收錄 127 個字元。GBK 字符集是常用的中文字符集，共收錄兩萬多個漢字。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**：原碼轉二補數的方法是“先取反後加 1”，那麼二補數轉原碼應該是逆運算“先減 1 後取反”，而二補數轉原碼也一樣可以透過“先取反後加 1”得到，這是為什麼呢？

這是因為原碼和二補數的相互轉換實際上是計算“補數”的過程。我們先給出補數的定義：假設 $a + b = c$ ，那麼我們稱 $a$ 是 $b$ 到 $c$ 的補數，反之也稱 $b$ 是 $a$ 到 $c$ 的補數。

給定一個 $n = 4$ 位長度的二進位制數 $0010$ ，如果將這個數字看作原碼（不考慮符號位），那麼它的二補數需透過“先取反後加 1”得到：

$$
0010 \rightarrow 1101 \rightarrow 1110
$$

我們會發現，原碼和二補數的和是 $0010 + 1110 = 10000$ ，也就是說，二補數 $1110$ 是原碼 $0010$ 到 $10000$ 的“補數”。**這意味著上述“先取反後加 1”實際上是計算到 $10000$ 的補數的過程**。

那麼，二補數 $1110$ 到 $10000$ 的“補數”是多少呢？我們依然可以用“先取反後加 1”得到它：

$$
1110 \rightarrow 0001 \rightarrow 0010
$$

換句話說，原碼和二補數互為對方到 $10000$ 的“補數”，因此“原碼轉二補數”和“二補數轉原碼”可以用相同的操作（先取反後加 1 ）實現。

當然，我們也可以用逆運算來求二補數 $1110$ 的原碼，即“先減 1 後取反”：

$$
1110 \rightarrow 1101 \rightarrow 0010
$$

總結來看，“先取反後加 1”和“先減 1 後取反”這兩種運算都是在計算到 $10000$ 的補數，它們是等價的。

本質上看，“取反”操作實際上是求到 $1111$ 的補數（因為恆有 `原碼 + 一補數 = 1111`）；而在一補數基礎上再加 1 得到的二補數，就是到 $10000$ 的補數。

上述以 $n = 4$ 為例，其可被推廣至任意位數的二進位制數。
