ついにヒープソートなんだけど、その前に選択ソート
選択ソート - Wikipedia最小値を探し出して順番に並べるだけ
https://ja.wikipedia.org/wiki/%E9%81%B8%E6%8A%9E%E3%82%BD%E3%83%BC%E3%83%88
最初は配列全体から最小値を探す、見つかった値は配列の1番左と交換
次は左から2番目以降から最小値を探す、見つかった値は左から2番めと交換
次は左から3番目以降から...これを繰り返して右端まで行ったら完了
比較する回数はバブルソートと変わらないから遅いのかなあと予想しつつ
1万件をソートの時間計測
4.4804秒
バブルソートは10秒だったから2倍以上速い!
コード的にはバブルソートより簡単な気がするんだけどね、速い
ここから本番のヒープソート
ヒープソートと選択ソートに共通しているのが最小値(か最大値)を探し出して順番に並べるってところ
違うのは探す方法
ヒープソートはヒープ(ヒープ構造)ってのを使って探す
ヒープってのは日本語だと二分木らしい
けどどっちも聞いたことないよ
形で言うとこういうのがヒープらしい親と子があって親のしたには子が2つある状態、
親から2つ分かれているから二分木なのかも
これだと要素を3つしか扱えないけど、子の下にさらに子を付け足していくことができる、親から見たら孫みたいね
こんなふうにどんどん増やせる
番号を付ける
上から下へ、左から右へ順番に番号をつけていくと都合がいい
0番の子は1番と2番ってことになって
1番の親は0番、1番の子は3番と4番
5番の親は2番ってことがわかる
中途半端な変な形
要素数が増えて8個の場合はこうなる
3番の子は7番しかないけどこの形でもヒープ
順番に番号をつけられたってことは配列と同じように使えるってこと
v = Array(1, 3, 2)
っていう配列をヒープに当てはめていくと↓になる
●の右上の数値は番号で
●の中は配列の値
ヒープには親が上で子が下にあるっていう位置関係以外にもルールがあって
親の数値>=子の数値
親は子の数値より大きいことがヒープの条件
さっきの配列をヒープに当てはめたのを見ると、0番の数値1は子の数値3と2よりも小さいのでヒープじゃないってことなる
正しいヒープに修正するには上から見ていく方法と下からがあるみたい
今回は上から見ていくので0番から
0番の子は1番と2番、どちらの数値も親より大きいけど、より大きい方と交換する
交換したところ
これで正しいヒープになった
こういうのを繰り返していくと一番上が最大値になるのでこれを取り出して順番に並べると整列できるってのがヒープソートみたい
自分の親や子の番号を求める方法
親の番号を求める
親や子と比較するときはその番号が必要になる配列の添字が0からの場合の自分の親の番号の求め方
(自分の番号-1)\2
\は割り算の商部分(小数点以下切り捨て)なので
例えば自分の番号が4なら親の番号は
(4-1)/2=1.5=1
図で見ても4番の親は1番であっている
0番から14番までの親の番号一覧で確認
Quotientは割り算の商を返す
(自分の番号 + 開始添字 - 1) \ 2
になる
4から始まる配列で7番の親番号は
(7+4-1)/2=5
5番であっている
これも一覧で確認してみる
1番から始まるときもこれでOK
今度は子の番号を求める
左の子=自分の番号*2+1
右の子=自分の番号*2+2、または左+1
自分が2番のときの子の番号は
左=2*2+1=5
右=2*2+2=6
あってる
これも添字が0からの配列の場合だけなので
0以上から始まる配列のときは
左の子=自分の番号 * 2 - (開始添字 - 1)
右の子=自分の番号 * 2 - (開始添字)、または左+1
これで準備が整ったので配列をヒープにしていく
添字は0から始まる配列Array(5, 3, 1, 4, 6, 2)を0番から順番に
0番に5を入れて、1番に3を入れたところ
親の番号は
(自分の番号-1)\2
なので
(1-1)\2=0番
親は0番
親(5)>子(3)のルールはあっているので次へ
2番に1を入れたところ
2番の親の番号は(2-1)\2=0で0番
比較して
これもあっているので次へ
3番に4を追加
3番の親の番号は(3-1)\2=1で1番
比較すると自分(子)のほうが大きいので交換
交換した
交換した先の関係を見る
1番の親の番号は0番
比較すると今度はあっているので次へ
4番に6を追加
4番の親の番号は(4-1)\2=1で1番
比較すると自分(子)のほうが大きいので交換
1番と4番を交換した
交換した先の関係を見る
1番の親の番号は0番
比較すると子のほうが大きいので交換
0番と1番を交換した
交換した先の関係を見る
0番の親はないので次へ
5番に次の値(2)を追加した
5番の親の番号は(5-1)\2=2で2番
比較すると子のほうが大きいので交換
2番と5番を交換した
交換した先の関係を見ると
2番の親の番号は0番
比較するとあっているので次へ
配列の最後の要素まで来たので終了
元の配列の並びはArray(5, 3, 1, 4, 6, 2)こうだったのが
(6, 5, 2, 3, 4, 1)となった、まだ整列(ソート)できていないけどヒープ構造にはなっている
一つ一つの最小単位のヒープでは親の値>子の値というルールは守られている
この状態になると一番上(0番)には最大値が入るはずなので
最大値が判明したことになる
外側のループはFor~Nextをつかっている、1から開始して配列の最後までループ、0からじゃないのは0番の親はないから
内側のループはDo While~Loopをつかっている、追加した自分の親の値と比較して自分のほうが大きければ交換、交換した先でも親と比較して必要なら交換を繰り返して自分のほうが小さくなるまでこのループを続けるのでループ条件は
親の値 < 自分の値、v(p) < v(ei)
ループを抜けたら外側のループなので2番3番と同じように追加していく
Array(5, 3, 1, 4, 6, 2)を渡すと
(5, 3, 1, 4, 6, 2)→(6, 5, 2, 3, 4, 1)
図で見ると
こうなった、あっている
0番には最大値が入っているし小さな単位のヒープは親の値 > 子の値という正しい状態
次はは整列していく作業
最大値がある0番と最後尾の5番を交換する
5番は最大値が入ったので整列済みになるので以降は無視して、残りの0番から4番までを作業の対象にする
次は交換した0番のせいでヒープが崩れてしまったので修正していく、0番から見ていく
図を見れば0番と1番を交換すればいいのがわかる
まず子同士の比較をして大きい方と親(自分)を比較する
子のほうが大きければ交換、小さければそのまま
0番は一番上で必ず親なので子の番号を求める
子の番号は
左の子=自分の番号*2+1
右の子=自分の番号*2+2、または左+1
なので
左=0*2+1=1
右=0*2+2=2
1番と2番を比較して大きいのは1番
1番と0番を比較して1番のほうが大きいので交換になる
交換した
次も同じように子とのヒープ状態をみて正しくなければ交換していく
図を見ればわかるけど1番と4番を入れ替えれば正しくなる
1番の子の番号は
左=1*2+1=3、右=1*2+2=4、比較して4番のほうが大きい
4番と自分を比較して4番のほうが大きいので交換
交換した
4番の子は無いのでこれで修正は完了
ヒープが正しくなると0番に最大値が入っていることになるので
0番と最後尾の4番を交換する
最後尾といっても5番じゃなくて4番になるのは
5番はもう整列済みで無視するところだから
交換した
これで5番に続き4番も整列済みなので以降は無視、対象外
次はまた0番を交換したせいでヒープが崩れたので
0番から修正していく
0番と1番を交換した
1番の子は3番と4番になっているけど4番は整列済みなので
3番と比較、交換になる
1番と3番を交換した
修正完了したので0番と最後尾を入れ替える
交換した
これで3番から5番まで整列済み
また0番から修正していく
0番と1番を交換
1番の子は整列済みなので修正完了
0番を最後尾と交換する
交換した
今度は0番を見てもヒープは正しいので修正無しでOK
0番と最後尾を交換
交換した
0番以外は整列済みで比較対象がなくなったので
全て整列済みになる
ヒープソート完成!
ここまでの修正しながらの整列をVBAにすると
ループは2つ、外側のループは要素の総数から1づつ減らしていって1になるまでループ、減らしていっているのは整列済みがどんどん増えていくからその分を除くため
内側のループはDo~Loopでループ条件の
p * 2 + 1 < eiは
左の子の番号 < 最後尾番号
つまり自分の子がなくなるまでループ
内側ループの前半は子同士の比較をして大きい方の番号を取得
後半はその大きい方の子と自分を比較して子が小さければ修復完了なので内側ループ抜けして、大きければ交換してループを続ける
内側ループが終わったら最大値と最後尾を交換して外側ループ
最後尾番号が1になったらソート完了
これでソート部分もできたのでさっきの配列をヒープにしていくのと合わせるとヒープソートの完成↓
やっとできたヒープソートはどれくらい速いのか
ランダム数値の1万件ソート
5回計測、平均すると0.085秒
速い!
今までのまとめ
だんだん長くなってきた
グラフで
コムソートとシェルソートには及ばずだけど
前回のマージソートよりは速い結果になった
バブル、シェーカーの遅い組
挿入、選択の中間組
コム、シェル、マージ、ヒープの速い組
3つに分かれる感じかなあ
100万件ソート
12.6秒
あれ、遅い?
1万件ではマージソートより速かったのに
100万件にしたら逆転してしまった
参照したところ
Programming Place Plus アルゴリズムとデータ構造編【データ構造】 第9章 ヒープ
http://ppp-lab.sakura.ne.jp/ProgrammingPlacePlus/algorithm/data_struct/009.html
VBA ヒープソートを実装 ~関数を沢山作って複雑な問題に対処する - t-hom’s diary
http://thom.hateblo.jp/entry/2017/03/20/202940
最初できたのは1万件をソートで10分(815秒)以上もかかるものだったw
そこから一応これくらいなのかなっていう1秒台まで縮めたあとは一旦諦めてマージソートを書いていたみたいで、その後に何か思いついて0.1秒になって今回の0.085秒までは1週間くらいかかったのかなあ、それでも理解できていないから本当はもっと速いのかも、でももうヒープソートはお腹いっぱい
残るは最速と言われているクイックソートなんだけど、これが全然わかんなくて3秒もかかるものしか書けてない
'開始添字が0以上でもOKのヒープソート
ほとんど一緒で違うのはループの条件とループの開始位置
これを使ってSheet3のA1からA10000をソートしてB列に貼り付けるマクロ
次回
エクセルVBAでクイックソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14831821.html
関連記事
エクセルVBAでマージソートと再帰処理(再帰呼出し)...も難しいなあ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14807202.html
始まり
エクセルVBAでバブルソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14787146.html
エクセルVBAで、ソートアルゴリズムとデータの違いによるソート処理時間比較 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14836198.html