データ構造とアルゴリズム
第八回
(2014年11月14日)
辞書とその実装: 二分木など
http://www.sw.it.aoyama.ac.jp/2014/DA/lecture8.html
Martin J. Dürst
© 2009-14 Martin
J. Dürst 青山学院大学
目次
- 前回の残り・まとめ・宿題
- O(n log n) より早い整列法
- 抽象データ型の辞書
- 二分木とその辿り方
- 二分探索木
- 平衡木
前回の残り
前回のまとめ
- クイックソートは効率のよい整列法
- 最悪の場合に O(n2); 平均で
O(n log n)
- 分割要素の選択など実装で注意点が多い
- 実装例: 7qsort.rb
- 様々な整列法のアニメーション: sort.svg
レポートの課題
手作業の場合に効率的な順列アルゴリズムを考えて提案して分析しなさい。
条件は次の通り: 資料がそれぞれ A4
の紙一枚。見やすい位置に10桁の番号が書かれてある。その番号の昇順にソートする。番号の分布については一切不明。二つのケースを想定:
- 一人で資料6000枚をソート
- 24人で資料60000枚をソート
共通のアルゴリズムでも別々のアルゴリズムでもよい。分析では
O() での計算量 (理由つき)
と実際に想定される時間、今まで習ったアルゴリズムとの類似点や相違点、手作業の特徴を考慮した点などについても論じる。
レポートの「失敗」の例
- 218341.368秒 (⇒約61時間)
- 61010·103·1010 (単位? 桁違いか)
- O(60000) (これは何秒か)
- O 記法から逆算
(1秒の作業、n=6000、O(n2)
⇒ 3600000秒?)
- O(n) のアルゴリズム (例: 一枚当たりの作業に5秒)
- 24人の場合、最後に一人だけ働かせる
- 人間にとって、二分が窮屈 (3~10分が最適)
- バブルソート (休憩、睡眠なしで
868日間)
- 箱を1010個用意する (問題:
場所、費用、歩く距離)
- 準備、整理、休憩の考量無し
- プログラムのみ提出
- 一ページ未満
O(n log n) より早い整列法
- 今までの整列法は値の任意な分布に対応
- 比較の決定木で最低 O(n log n)
- 値の分布について前知識がある場合、改善可能
- 極端な例: 1 から n までの数の整理
→最終的な場所が完全に予想可能
→O(n)
- 基数整列 (radix sort)、
ビンソート (bin sort, bucket sort) など
ビンソート
例: 学生番号で整列
- 最上位の桁で 10 の山に分割
- 各々の山を再帰的に順に下位の桁で分割
- 山の大きさに対応するため、
分割を二つのフェーズで実行
(8binradix.rb の
one_digit_stable_sort
)
- 各々の山の大きさを計算
- 要素の再配置
- 計算量は O(n
k) (k は桁の数)
- Ruby での実装: 8binradix.rb の
bin_sort
基数整列
(radix sort)
- 最下位の桁から桁ごとに整列
- 上位の桁で分割する必要がない
- 安定な整列法が必要
- 計算量は O(n
k) (k は桁の数)
- Ruby での実装: 8binradix.rb の
radix_sort
辞書の抽象データ型
(dictionary; 注: 実際の辞書とは違う)
- データ項目ごと
- キー (key): 項目の特定のため、探索に使用
- 値 (value): キー以外の項目ごとの情報、無しも可
- 操作
- 挿入 (insert)
- 削除 (delete)
- 探索 (search/find)
辞書の簡単な実装
- 整列済の配列: 探索は二分探索で O(log
n)、挿入・削除は O(n)
- 未整列の配列、連結リスト: 探索は O(n)
- 挿入・削除・探索を全て O(log
n) 以内で実装したい
二分木
- グラフ (graph): 頂点 (ノード、node) と辺 (edge)
からなる
- 木 (tree): 根 (root) は親 (parent)
が無いが、他の頂点が全て一つの親と接続
- 二分木 (binary tree): 各頂点に子 (child) が最大 2 個
二分木の辿り方
- 深さ優先 (depth first)
- 行きがけ順 (preorder)
- 通りがけ順 (inorder)
- 帰りがけ順 (postorder)
- 幅優先 (breadth first)
二分探索木
(定義・普遍条件)
- 各頂点にデータ項目を一つ配置
- 任意の頂点のキーが k の場合
- 左の部分木のキーが全て k より小さい
- 右の部分木のキーが全て k より大きい
- 同等なキーが複数ある場合の扱いは実装依存
探索木での探索
- 根から探索を開始
- 現在の頂点のキーと比べ探索のキーが
- 同じ: 頂点のデータ項目を返す
- 小さい: 左の部分木を探索
- 大きい: 右の部分木を探索
- 空の頂点: 探索終了
探索木への挿入
- 根から挿入を開始
- 現在の頂点のキーより挿入のキーが
- 小さい: 左の部分木に挿入
- 大きい: 右の部分木に挿入
- 空の頂点: 挿入の頂点に置換 (その子は空の頂点)
- 同じ: 挿入打ち切り、右の部分木に挿入、など
探索木からの削除
- 削除したい頂点を発見 (探索同様)
- 削除したい頂点に (真の) 子が
- ない: そのまま削除
- 一個だけ: それと置換
- 二個: 右の部分木の一番小さいキーの頂点と置換
探索木の実装
- 子が無い場所、特殊なノード (
NilNode
)
を全てのノードで共有
- Ruby での実装: 8bintree.rb
単純な探索木の評価
- 木全体の高さで操作の計算時間が決まる
- 最善の高さが O(log
n)
- 最悪の高さが O(n)
- 平均の高さが O(log
n)
平衡木
(balanced tree)
- 一般の探索木は最悪、連結リストの形を同等
- 挿入・削除の順番が決まっているため、
(クイックソートのように)
乱数などで分割項目を決めることは不可能
- 完全二分木の場合、追加・削除の手間が課題
解決策: 完全ではないがある程度平衡性を保つ木
トップダウン 2-3-4 木
(top-down 2-3-4 tree)
- 各ノードの子数は 2、3 または 4
- 子数が k の場合、ノードに k-1
のキーとデータ項目を保持
(子数が全て 2 の場合、二分探索木と同等)
- ノード内のデータ項目のキーは部分木の分岐点
- 木の高さは一定
- 一番下の層には子がない
(実装上、全部同一の空のノード)
次回への宿題
(提出無し)
- データ項目が n
の場合の最低と最大の二分探索木の深さを考える
- 2-3-4
木の追加の操作を複数の例を使って考えて、アルゴリズムを提案