* 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.5 KiB
二分木の配列表現
連結リスト形式では、二分木の格納単位はノード TreeNode で、ノード同士はポインタでつながっています。前節では、この形式の二分木の基本操作を紹介しました。
では、二分木を配列で表すこともできるでしょうか。答えは、できます。
パーフェクト二分木の表現
まず単純な場合を考えます。パーフェクト二分木の全ノードをレベル順に配列へ格納すると、各ノードが一意のインデックスに対応します。
レベル順走査の特徴から、親と子のインデックスの対応式を導けます。ノードのインデックスが i なら、左の子は $2i + 1$、右の子は 2i + 2 です。下図に対応関係を示します。
この対応式は、連結リストのノード参照(ポインタ)と同じ役割を果たします。配列内のどのノードからでも、式を使って左右の子にアクセスできます。
任意の二分木の表現
パーフェクト二分木は特殊な場合です。一般の二分木では、中間の層にも多くの None があります。レベル順の列にはそれらが含まれないため、その列だけでは None の数や位置を推測できません。つまり、同じレベル順の列に対応する二分木の構造が複数あります。
下図のように、パーフェクト二分木でない木には、上の配列表現をそのまま使えません。
解決するには、レベル順の列にすべての None を明示的に書き込みます。下図のように、これで二分木を一意に表せます。コード例を示します。
=== "Python"
```python title=""
# 二分木の配列表現
# None で空の位置を表す
tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
```
=== "C++"
```cpp title=""
/* 二分木の配列表現 */
// int の最大値 INT_MAX で空の位置を示す
vector<int> tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};
```
=== "Java"
```java title=""
/* 二分木の配列表現 */
// int のラッパークラス Integer を使うと、null で空の位置を示せる
Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };
```
=== "C#"
```csharp title=""
/* 二分木の配列表現 */
// null 許容型 int? を使うと、null で空の位置を示せる
int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
```
=== "Go"
```go title=""
/* 二分木の配列表現 */
// any 型のスライスを使うと、nil で空の位置を示せる
tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15}
```
=== "Swift"
```swift title=""
/* 二分木の配列表現 */
// オプショナル型 Int? を使うと、nil で空の位置を示せる
let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]
```
=== "JS"
```javascript title=""
/* 二分木の配列表現 */
// null で空の位置を表す
let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
```
=== "TS"
```typescript title=""
/* 二分木の配列表現 */
// null で空の位置を表す
let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
```
=== "Dart"
```dart title=""
/* 二分木の配列表現 */
// null 許容型 int? を使うと、null で空の位置を示せる
List<int?> tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
```
=== "Rust"
```rust title=""
/* 二分木の配列表現 */
// None で空の位置を示す
let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)];
```
=== "C"
```c title=""
/* 二分木の配列表現 */
// int の最大値で空の位置を示すため、ノードの値に INT_MAX は使えない
int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};
```
=== "Kotlin"
```kotlin title=""
/* 二分木の配列表現 */
// null で空の位置を表す
val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )
```
=== "Ruby"
```ruby title=""
### 二分木の配列表現 ###
# nil で空の位置を表す
tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]
```
完全二分木は、配列で表すのに非常に適しています。定義を思い出すと、None は最下層の右側にしかありません。したがって、すべての None はレベル順の列の末尾に現れます。
つまり、完全二分木を配列で表す場合は、None の格納をすべて省けるので便利です。下図に例を示します。
以下は配列で二分木を実装したコードで、次の操作を含みます。
- 指定したノードの値、左右の子、親を取得します。
- 行きがけ順、通りがけ順、帰りがけ順、レベル順の走査列を取得します。
[file]{array_binary_tree}-[class]{array_binary_tree}-[func]{}
長所と制約
配列表現の主な長所は次のとおりです。
- 連続したメモリに格納されるのでキャッシュとの相性がよく、アクセスと走査が高速です。
- ポインタの格納が不要なので、領域を節約できます。
- ノードにランダムアクセスできます。
一方、次の制約もあります。
- 連続したメモリが必要なので、データ量が非常に多い木には適しません。
- ノードの追加・削除には配列の挿入・削除操作が必要で、効率が低くなります。
Noneが多い二分木では、配列内で実際のノードデータが占める割合が低く、領域の利用効率が下がります。



