二進木(二分木)

アルゴリズムとデータ構造
第3章 ヒープ
6月3日分
情報知能学科
白井英俊
二進木(二分木)とは
高々2個
• Binary tree の訳語
定義: どの頂点も2個以下の子をもつ木
2個の子は左の子と右の子のどちらか
左
右
0個の子
1個の子
2個の子
左
右
頂点の深さと木の高さ
• 根と頂点の間の枝の数が「頂点の深さ」
• 最も深い頂点の「深さ」が「木の高さ」
根=深さ 0
深さ 1
深さ 2
深さ 3
深さ 4
この木の
高さ= 4
練習問題
(1) 「3」の頂点の深さは?
(2) この木の高さは?
8
5
3
1
10
7
9
12
二進木のいろいろ
(1) 高さ 0 の二進木
根一個からなる木も二進木
(2) 高さ 1 の二進木 ...3通り
左
右
左
(3) 高さ 2 の二進木 は…
右
高さ 2 の二進木
左
左
左
右
右
左
左
右
左
左
右
右
右
右
左
左
左
右
右
右
右
…などなど
二進木の演習
• 高さ2の二進木をすべて書いてみよう。
何個あるだろうか?
考え方ー場合分け:
(1) 根からみて左の部分木の高さが2の場合:
右の部分木の高さが0, 1, 2に分けると
(a)右の部分木の高さが0: 1通り
(b)右の部分木の高さが1: 1通り
(c)右の部分木の高さが2: あとで計算
ここで左の部分木の高さが2の場合は3通り
だから合計 2*3 = 6通り
(2)根からみて右の部分木の高さが2の場合: (1)と同様 6通り
(3)根からみて右と左の部分木がともに高さ2の場合: 3*3=9通り
高さ 2 の二進木
(1)
(2)
(3)
(8)
(7)
(10)
(11)
(4)
(5)
(6)
(9)
(12)
高さ 2 の二進木(続き)
(13)
(16)
(19)
(14)
(17)
(20)
(15)
(18)
(21)
二進木のデータ構造
• 二進木の場合、子は左か右しかない
だから、「一般の木」よりも特殊なデータ構造を
考えるのが便利
「引数=値」とは、引数が省略できること、省略
された場合に「値」となることを意味する
class Vertex
def initialize(data=“ “, left=nil, right=nil)
@data = data
@left = left
@right = right
end # initialize
attr_attribute :data, :left, :right
end # class
二進木用のデータ構造を図示すると
図で書くと二進木を構成するデータ構造は
data
left
数または文字列
right
ポインタ( nilのことも)
または
ポインタ( nilのことも)
data
left
right
二進木の表現
4
4
3
3
5
2
1
2
8
6
1
7
5
8
6
7
二進木を用いたソート(sort)
 二進木を利用して、n個の実数を小さいものから
大きいものへと昇順に並べる(ソート, sort)こ
とを考えよう
 このために用いる二進木を「二分探索木」
(binary search tree)と呼ぶ
 与えられた実数を1個ずつ読み込み、それぞれを
頂点に格納しながら、二分探索木を作っていく
 すべての実数に対応する頂点をもつ二分探索木
の完成後、すべての頂点を中間順で読みだす。
これによりソートが完成される
二分探索木を作る…
• 入力データが {4, 5, 8, 3, 6, 10, 2, 9, 1} とする。
どのように二分探索木を作るか?
• 最初だけ特別:先頭の要素を値に持つ根
の頂点を作る
root = Vertex.new(4)
ここでは図を書いて、木ができていく様子
を見よう
二分探索木を作る…(続き)
root = Vertex.new(4) によって以下の
頂点が作られる:
4
または
4
その次の要素を x とすると、
頂点の値 > x ならば、左の部分木を
さもなくば
右の部分木を
成長させる 新たな頂点を作って、元の頂点
と接続する
二分探索木を作る…(続き)
入力データ:{4, 5, 8, 3, 6, 10, 2, 9, 1}
または
4
4
に続く要素は 5 で、4 < 5 なので、次のよ
うに「右の部分木」を成長させる:
4
4
元の頂点と接続
5
新たな頂点
5
二分探索木を作る…(続き)
2<4だ
3<4だ
から左
から左
2<3だ
から左
2
1
3
4<10
4<5だ
入力データ:
4<8だ
4
4<6だ
から右
から右
から右
から右 { 4 5 8 3 6
5 5<8だ
5<10
5<6だ
}
10
2
9
1
から右
から右
から右
6<8だ
8
8<10
から左
から右
6
10
9
どこにノードがで
きるか予想しよう
練習:二分探索木を作る
• 先のスライドにならって、次のデータから二分
探索木を求めよ。
{ 5, 9, 2, 7, 1, 3, 6, 8, 4}
頂点のラベル
注:表記法について
右の木に対し下のように書いてもよいとする
4
[4, [3, [2, [1],nil ],nil ],
[5, nil,
[8,[6,nil,[7]],
nil]]]
3
5
2
1
左の木
8
6
右の木
7
練習問題の解答{ 5, 9, 2, 7, 1, 3, 6, 8, 4}
入力データ:
5
2
1
9
3
7
4
6
{5 9 2 7 1
3 6 8 4}
配列を用いて表すと:
8
[5, [2, [1], [3, nil, [4]]],
[9, [7, [6], [8]], nil]]
二分探索木の特徴
(1) n個の頂点の二分探索木の高さは log(n)に比例
(2) したがって、二分探索木上での探索は O(log(n))
根
R
S
SS
Sよりも小さい
B
SB
Sよりも大きい
根(R)より小さいもの
BS
BB
Bよりも小さい Bよりも大きい
根(R)よりも大きいもの
木に要素xを加えるインスタンスメソッ
ドinsert(x)
class Vertex
def initialize(data=“ “, left=nil, right=nil)
@data = data
@left = left
@right = right
end # initialize
に続けて…
def insert(x)
# xは付け加える要素
1)xがdata より小さいなら if x < @data
a) 左の子がなければ
if (@left == nil)
xの値をもつ新たな頂点
@left = Vertex.new(x)
を作成し左の子とする
else
b) さもなくば
@left.insert(x)
end
2) 1)でなければ
右の子に対して同様にする
else
…
インスタンスメソッドinsert(x) 続き
def insert(x)
if (x < @data)
# x がtの値より小さい場合
if (@left = = nil)
# t の左の子がないなら
@left = Vertex.new(x)
# xを値とする頂点を
左 else
# 作って t の左の子に
left
@left.insert(x)
# 左の子に対し再帰
end #if
# xがtの値以上の場合
elsif (@right = = nil)
# t の右の子がないなら
@right = Vertex.new(x)
# xを値とする頂点を
右 else
# 作って t の右の子に
right
@right.insert(x)
# 右の子に対し再帰
end # if
end # def
プログラムによる確認
• ウェブページにある June03.rb を走らせて
みる
「木のなぞり」、または値の読出し
頂点の値が真ん中の位置で読
み出されるので「中間」順
• 中間順の読出し
左の子、根頂点の値、右の子
という順に、値を読み出す
・参考:先行順、後行順、もある
例 スタートはいつ
根頂点(2)
8
も根が基準
左(1)
5
だから、 5, 8, 10
10 右(3)
部分木について
右は「8」を根とする木
これは、「5」を根とする木
と「10」を根とする木を、
それぞれ左と右に接続し
た木、とみなせる。
それぞれ、頂点「8」
から見て、左部分木
と右部分木と呼ぶ。
3
1
8
5
10
7
9
12
中間順の「木のなぞり」の例2
根頂点(2)
8
左(1)
5
3
10
7
スタートはいつ
も根が基準
右(3)
20
「左部分木」は(部分)木なので、
中間順が適用され:
根の「右部分木」は
(部分)木なので、中間
順が適用され:
練習:中間順の木のなぞり
8
5
3
1
10
7
9
12
他の「木のなぞり」
• 先行順: 「根頂点の値」、左の子、右の子
• 後行順: 左の子、右の子、 「根頂点の値」
先の演習問題の木に対し、先行順と後行順の
それぞれで「木をなぞる」と、どのようになる
か?
練習:先行順と後行順の木のなぞり
8
5
3
1
10
7
9
12
応用問題:数式の木
例: (a+b)*(c-d*e) は次の二進木で表現される(葉に
は変数が、葉以外の頂点は演算子が書かれる)
*
+
a
ー
b
*
c
d
e
数式の木の問題
• 先の木を
1) 中間順(inorder)
2) 先行順(preorder)
3) 後行順(postorder)
でそれぞれなぞった時にはどのような記
号列が表示されるか?
参考:ポーランド記法、逆ポーランド記法
中間順に木をなぞるプログラム
class Vertex
def initialize …
attr_attribute :data, :left, :right
def inorder
# 中間順でなぞる
@left.inorder if @left != nil
# vの左の子をなぞる
print @data
# 頂点のラベルを表示
@right.inorder if @right != nil # vの右の子をなぞる
end # def of inorder
練習問題:頂点のデータを要素
end # class
とする配列を返すプログラムに
作り変えてみよ。注意:
先行順(preorder)、後行順(postorder)のプログ
このプログラムは頂点のデータ
ラムはどう書ける? を次々にprintするだけなので、
returnを用いていない。
2進木を用いたソート
入力データから insert によって二分探索
木をつくり、最終的にできた木の頂点を「中間
順」に読み出すと…
入力データの最も小さいものから大きいものへ
順に入力データが並ぶ…ソートされている!
これはどのような計算量で実行可能か?
二分探索木を作り、中間順に読み出す
入力データ:
4
3
5
2
1
{4 5 8 3 6
10 2 9 1 }
中間順に読出し:
8
6
10
9
2進木によるソート
• 最悪のケース:教科書p.28図32の様な場合
n個のデータから二分探索木を作る ⇒ O(n2)
二進探索木を中間順に読み出す ⇒ O(n)
合計: O(n2)
• 平均的に(特にデータをシャッフルすると)n個
のデータから作られる木の高さは O(n log n)
⇒ 従って二分探索木を作る計算量も O(n log n)
理由: n個のデータに対し、高さに比例した個数
の枝をたどって頂点を付け加えるから
練習問題(宿題)
問題1.中間順に木をなぞるプログラムを参考にして
、後行順および先行順で木をなぞるインスタンスメ
ソッドを作る
問題2.中間順で木をなぞるプログラムを改変して、
「葉」の値だけを取り出すインスタンスメソッドを作
る。
8
例:右の木では、 1, 7, 9, 12 が
5
出力される
問題3.所与の頂点を根に持つ 3
木の高さを返すメソッドを作る
1
10
7 9
12