Chap3-4

アルゴリズムとデータ構造
第3章 ヒープ
6月10日分
情報知能学科
白井英俊
ヒープ
• ヒープ(heap)とは、特殊な二進木
定義3.2 高さkの二進木Tがヒープであるための
必要十分条件は次の(i), (ii)を満たすこと
(i) 深さ0 ~ k-1 の可能な頂点はすべて使われ、
深さkの頂点は左から順に使われている
(ii) 頂点uが頂点vの親なら
uの値( f(u) ) ≧ vの値( f(v) )
親
子
ヒープの特徴
頂点の番号のつけ方:
1
可能な頂点すべてに、
3
2
4
8
5
9
6
上から下、左から右
7
10
この番号の付け方から、高さ n (> k)のヒープでは、
★深さ k の最も左の頂点の番号は 2k
★深さ k の最も右の頂点の番号は 2(k+1) - 1
配列を用いたヒープの表現
このように、ヒープは「配列」を使って簡単に表せる
0
1
①
根
2
深さ1
3
②
③
4
深さ2
⑤ ⑥
5
⑦
④
6
⑨
7
⑧
深さ3 8
9
{
偶数:
左の子
奇数:
右の子
…
…
二進木による表現
{
{
根以外は
配列による表現
ヒープへの要素の追加(挿入)手続き
手続き:以下の2ステップで行う
(1) 新たな要素を値とする頂点をヒープに追加
⇒ 条件(i)を満たすところに追加
(2) 次に、ヒープ条件(ii)を満たすように修
正(reform)する
ヒープへの要素の追加(1)
ヒープの条件(i)を満たすように要素を追加する
ことは、
(b)二進木で表す場合、「可能な頂点を左から順
に使う」
(a) 配列で表す場合は分かりやすい
⇒ ヒープの最後の要素のインデックスを
x とすれば、x+1 番目に要素を追加すれ
ばよい(最後尾に付け加える)
ヒープへの要素の追加(1)
例:
②
④
8
①
3
①
8
②
5
③
④
7
3
⑤
10
に3を追加する…
③
5
7
⑤
10
それには、ここしかない
そして、ヒープ条件(ii)を満たし
ているのでヒープが完成
さらに、 10を追加する。。。
これは、ヒープ条件(ii)を満たして
いないので『修正』が必要
上の二進木の配列表現
ヒープへの要素の追加(2)「修正」
• 追加した頂点を v とする
• ヒープの条件(ii)は、
親頂点の値≧子頂点の値
だから、これが満たされていればOK.
そうでなければ、上の条件を満たすように、頂
点を入れ替えていく(修正する)
ヒープへの要素の追加(2)続き
ヒープへの要素の追加の完成
②
右の二進木
の配列表現
④
①
8
②
5
③
7
④
3
⑤
10
3
条件を破っ
ている
8
①
③
5
7
⑤
10
条件を破っ
ている
ヒープを作る具体例
入力データ:
8
5
3
1
7
10
20
{ 8 5 7 3 10
9 6 1 20 }
9
6
ヒープ条件(ii)を破っ
ているので、『修正』
が必要
問題: ヒープ条件(ii)が成り立つ
ようにするには、どのように『修
正』したらよいか?
ヒープを作る具体例(配列版)
①
②
③
④
⑤
⑥
⑦
⑧
⑨
8
5
7
3
10
9
6
1
20
入力データ:
{ 8 5 7 3 10
9 6 1 20 }
ヒープ条件(ii)を破っ
ているので、『修正』
が必要
問題: ヒープ条件(ii)が成り立つ
ようにするには、どのように『修
正』したらよいか?
ヒープの演習
1. (a) 右の配列は、1~9番目までの要素は 0
ヒープの条件を満たす。そこに10番目の 1
要素として15を入れたとすれば、これは
ヒープの条件を満たしているか?その理 2
3
由も述べよ。
4
(b) 10番目の要素として15ではなく、
40が入っているとする。1~10番目ま 5
での要素からなる配列は、ヒープの
6
条件を満たしているか?その理由も
7
述べよ。
8
(c) (a)と(b)で、「ヒープになっていな
いもの」に対し、1~10番目までの要 9
10
素からなる配列をヒープにする(教
科書演習3.6)ことを考える。その結 11
果のヒープを示せ。
30
25
28
20
24
26
18
19
11
X
ヒープの演習(解答)
1. (a) 右の配列は、1~9番目まで
0
の要素はヒープの条件を満たす。 1
ということは、ヒープ条件(1)も(2)も満たしている
2
3
ヒープ条件(1)は、配列で書いた場合に、要素が
順々に入っているということ。
4
5
ヒープ条件(2)は、どの親の値も子どもの値より大
きい、ということ。
6
(a) そこに10番目の要素として15を入れたとすれば、
7
これはヒープの条件を満たしているか?
8
配列の要素を順々にいれているのだから、ヒープ
9
条件(1)は満たしている。条件(2)を調べればよい
10
(b) 10番目の要素として40が入るとする。1~10番目
までの要素からなる配列は、ヒープの条件を満たすか? 11
30
25
28
20
24
26
18
19
11
X15
40
大
小
ヒープの演習(解答)
(a)と(b)で、「ヒープになっていな
いもの」に対し、1~10番目まで
の要素からなる配列をヒープにす
る(教科書演習3.6)ことを考え
る。その結果のヒープを示せ。
0
1
30
2
25
28
3
4
20
5
24
n番目の要素の「親」は n/2 番目の要素
6
26
1
7
18
8
19
3
2
11
9
40
7
10
4
5 6
11
9 10
8
親と子を比較して必要なら入れ替える
ヒープソート(Heap Sort)
ヒープを用いてソート(並び替え)を行う
二分探索木によるソート
手続き:
(1) x1, x2, …, xn からヒープ(木)を作る と比較して「最悪の場合」
でもこれが保証されてい
るのが特長
計算量は O(n * log n)
ヒープ木においては根が最大の値の頂点
(2) 根と『最も深い頂点のうち最も右端の頂点』とを交
換し、『根だった頂点をヒープから切り離し』てから
ヒープを作り直す(remake)。この操作一回ごとの計
算量は O(log n)
(3) (2)を繰り返し、切り離された頂点をすべて並べると
ソート完成(新しく切り離された頂点が最小、古いも
のが最大)
ステップ1:ヒープを作る
入力データ:
8
5
3
1
7
10
20
{ 8 5 7 3 10
9 6 1 20 }
9
6
ヒープ条件(ii)を破っ
ているので、『修正』
が必要
前出の問題: ヒープ条件(ii)が成り立つ
ようにするには、どのように『修正』し
たらよいか?
答えは次のスライド…
ステップ1:ヒープを作る:結果
20
10
8
1
9
5
3
7
6
どの親子の組み合わせにおいても
ヒープ条件(ii)が成り立つ
ヒープソート(第2ステップ
(2)根と『最も深い頂点のうち最も右端の頂点』
とを交換し、『根だった頂点をヒープから切り
離し』てからヒープを作り直す(remake)。
(3) (2)を繰り返し、切り離された頂点をすべて並
べるとソート完成(新しく切り離された頂点が
最小、古いものが最大)
(1)のヒープの修正と紛らわしいので、
ここでは「ヒープの作り直し(remake)」と
呼ぶことにする
ヒープソート(続き)
「根と最後の要素を取り換え、ヒープを作り直す
(remake)」の繰り返し(ヒープソートのステップ(2))
20
10
8 ≧ 5
1
3
9
≧
7
1. 根と『最も深く右側の頂
点』とを交換し、根だった頂
点を切り離す
2. ヒープ条件(ii)を満たすよ
う、ヒープを作り直す
親頂点と、子頂点のう
ち大きいほうを交換。
6
問題: ヒープをどのように作り直すか?
問題: この後ヒープをどのように作り直すか?
ヒープソート(配列版)
①
③
⑤
⑥
⑦
⑧
⑨
≧
④
≧
②
20
10
9
8
5
7
6
1
3
「根と最後の要素を取り換え、ヒープを作り直
す」の繰り返し(ヒープソートのステップ(2))
問題: ヒープをどのように作り直すか?
1. 根と『最も深く右側の頂
点』とを交換し、根だった頂
点を切り離す
2. ヒープ条件(ii)を満たすよ
う、ヒープを作り直す
親頂点と、子頂点のう
ち大きいほうを交換。
問題: この後ヒープをどのように作り
直すか?
ヒープソートの真髄
ヒープを配列で表わす
⇒『木』よりも簡単なデータ構造で実行可能
インデックスによって、根の頂点を簡単に求めること
ができる
また、注目している頂点のインデックスから、その親
や子の頂点が何かが簡単に計算できる
問題: 注目している頂点のインデックスをxとすると、
その親と子の頂点のインデックスは?
ヒープの演習(続)
2. 以下の配列はヒープの条件を満たしているか?満たしていなければ(1)から
演習を行え。満たしているとすれば(2)から演習を行え。
(1) 1番目の要素から順に10番目までの要素をヒープにする操作を行い、最終
的に得られたヒープを示せ。
(2) 次に、1番目の要素と10番目の要素を入れ替え、10番目の要素を除いた
のこりの配列、つまり1~9番目の要素からなる配列に対し「ヒープの作り直
し」をせよ(演習3.7)
(3) これに続けて9番目の要素、8番目の要素、…,2番目の要素を順に1番目
の要素と入れ替えては(2)と同様の「ヒープを作り直す」操作を行い、最終的
に得られた配列を示せ。
(4) n 個の要素を持つ配列(1~n番目に要素があるとする)に対し、(1)の操作
でヒープを作る計算量のオーダーはどのくらいか、また(2)の手順で最終の
配列を得るための計算量のオーダーはどのくらいか?
1
2
30 25
3
4
5
6
20 16 24 18
7
8
9
12 10
8
10
15
プログラミング演習
• 問題1.1~n番目までの要素を持つ配列が与
えられているとする。
これがヒープの条件を満たしているかどうかを
判定するアルゴリズムとプログラムを作ろう。
条件1は当然成り立っているとすると、条件2が
成り立っているかどうかが問題。
1
2
30 25
3
4
5
6
20 16 24 18
7
8
9
12 10
8
10
15
プログラミング演習(問題1続き)
• 条件2は、どの頂点に対しても次が成り立てばよい。
親頂点の値≧子頂点の値
ここでn個の頂点があるヒープならば、「親頂点」で
あるのは、1(根)~n/2番目の頂点まで。
配列をxとすると:
for i in 1..n/2 # 親頂点を一つ一つ調べる
return false if (x[i] < x[i*2]) # 親頂点の値が子より小さい
return false if (i*2+1 <= n) && (x[i] < x[i*2+1])
end
return true # すべての親頂点が子よりも小さくない場合
プログラミング演習(問題1答)
def heapCheck(x)
n = x.size-1 # x.size は(n+1)を返すため
for i in 1..n/2 # 親頂点を一つ一つ調べる
return false if (x[i] < x[i*2]) # 親頂点の値が子より小さい
return false if (i*2+1 <= n) && (x[i] < x[i*2+1])
end
return true # すべての親頂点が子よりも小さくない場合
end
プログラミング問題
注意:これらはクラスメソッドを作る
のではなく、関数を作る問題
配列を用いてヒープソートするプログラムを作る。以下
は、そのための処理を構成するプログラムである。
(1)以下の条件を満たす関数reform_heapを作れ。入力は、
配列xとその最後の要素の番号n。ここで、x[1]~x[n-1]まで
はヒープの条件を満たしているものとする。そして、ヒープ
の条件を満たす配列xを出力する。 (難易度普通)
x[n]と、その親の要素の値とを比較し、ヒープ条件が守られて
いれば終了。さもなくば条件を守るように修正する。
(2)reform_heapを用いて、入力データ(数を要素とする配列)
からヒープ(配列で表現)を作る関数(makeHeap)を作れ。
(普通)
例:[8,5,7,3,10,9,6,1,20] という配列が入力として与えられた
時、 [nil, 20,10,9,5,8,7,6,1,3]というヒープ条件を満たす配列を
出力する
プログラミング問題(続き)
(3) ヒープ(配列で表現)において、根と最後の要素とを
交換する関数(swapNodes)を書け。(簡単)
swapNodesの入力(引数)を、配列xと、最後の要素
の番号nの二つとする。根はx[1]だから…
(4) 配列x、整数iとjを入力として、x[i] > x[j]ならばiを、
そうでなければjを返す関数(bigger)を作れ。 (簡
単)
これは次のremakeHeapで使う
プログラミング問題(続き)
(5) 配列xがヒープ条件を満たしており、その最後の要素の
インデックスを nとする。そして、 swapNodesによってx[1]
とx[n]の値を入れ替えたとする。そのような配列xとnを入
力(引数)とし、 x [1]~x[n-1] までの要素をヒープに作り
直した配列xを返す関数remakeHeapを書け。(やや難)
根とその子はヒープ条件を破っているはず。大きな値を持つ
子の要素と根の要素を入れ替える。入れ替えた要素とその
子もまたヒープ条件が成り立たないことがある。その時は、入
れ替えを繰り返す。こうしてヒープを作り直す。
(6) 以上を組み合わせて、ヒープソートを行う関数(heapSort)
を書け。(普通)
まずmakeHeapした後、swapNodesとremakeHeapを繰り返す。