ここから本文です
ベランダ菜園とWindows用アプリ作成とExcel用アプリ(アドイン)作成

書庫エクセルExcel2007

主にVBAで色々試したことのメモ
記事検索
検索

全12ページ

[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]

[ 次のページ ]

前回からの続き

2次の確率密度関数(ガウス関数)は1次同士を掛け算したものでいいらしい
変数xと変数yだった場合
イメージ 1
こうなるみたい

エクセルならNORMDIST関数2つの掛け算になって
=NORMDIST*NORMDIST
これだけ

標準偏差=1のときx,yが-2〜2までのガウス分布
イメージ 3
こうなって

イメージ 4
参照している場所はこんな感じで
等高線グラフにしてみると

イメージ 2
これは…前回の

イメージ 5
ガウシアンフィルタのカーネルにそっくり


もっとフィルタのカーネルっぽく
イメージ 6
一番小さい数値でそれぞれを割って
四捨五入したらそれっぽくなった、もう少し


標準偏差を1.05に
イメージ 7
よく使われるっていうカーネルの数値に近いのができた



NORMDIST関数を使わずに計算してみる
イメージ 8

結果
イメージ 9


イメージ 10
expの前だけ計算


イメージ 11
exp


イメージ 12
expの指数の部分


イメージ 13
expの指数の部分の参照状態


イメージ 14
全体を計算
NORMDIST関数と同じ結果になった!

イメージ 15
全体を計算の参照状態


イメージ 16
数値を整えてできた




1次から2次
2次の確率密度関数は1次同士を掛け算っていうことなので
イメージ 17
こうで
それぞれの1次は
イメージ 18
こう
これを掛け算だから

イメージ 19
こうなって、これをうまい具合に計算する


普通に計算できそうなのはexpの前の分数同士の掛け算
分子同士、分母同士を掛け算して
イメージ 21
おk、できた


expの右側はexpの指数だからべき乗同士の掛け算は
忘れたのでぐぐったら指数法則で
元になる数値が同じなら指数同士の足し算、これに当てはまって
分数同士の足し算で
イメージ 22
できた

expの前とあわせて
イメージ 20
完成
こういう中学校レベルでもググらないと解けないとかね


ワード2007大活躍
イメージ 23
数式の表示にワード使ったんだけど、この機能エクセルにも欲しいわ


もっと2次の正規分布っぽく!
イメージ 24
20x20マスと広げてみると曲線に近くなって、画像検索で出てくるグラフっぽくなる


標準偏差=0.5
イメージ 25
エクセルおもしろい

標準偏差=5
イメージ 26

標準偏差=4
イメージ 27
あれ?5より平面になった

もう一度標準偏差=5
グラフの縦軸を標準偏差=4のグラフと同じ範囲0〜0.01に変更
イメージ 28
ちゃんとよりフラットになっていた


標準偏差をxとyで別々に指定
x=0.5, y=5
イメージ 29

イメージ 30

イメージ 31

イメージ 32
ぼかし処理では使わないけどおもしろいねえ




次はC#で書けるのか、できたとして標準偏差の違いでぼかし処理がどう変わるのか、変わらないのか




参照したところ

整式の乗法 [指数法則・指数の計算・累乗の計算] / 数学I by ふぇるまー |マナペディア|
http://manapedia.jp/text/2432






関連記事
前回
エクセルで1次のガウス関数(確率密度関数)、正規分布関数のNORMDIST ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15942730.html

次回、2019/04/30は3日後
ガウス関数からカーネル作成、標準偏差とカーネルサイズ、グレースケール画像のぼかし処理 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15945699.html




1次元のガウス関数をエクセルで

前回の画像のぼかし処理で使った
3x3のガウスぼかしのカーネル
ガウシアンフィルタとかマスクとかも呼ばれる
イメージ 1
これ
等高線のグラフにして横から見る
イメージ 3

上から
イメージ 2

5x5のカーネルの場合
イメージ 4
中心の注目ピクセルに近いほど重みが増す
それも直線的に増えるんじゃないみたい

ここまでのカーネルは2次元、これが目的なんだけど、今回は1次元まで



画像のぼかし処理するだけならカーネルの数値さえ分かればいいんだけど
ググった先で読んでいると出てくるのが

ガウス関数とか確率密度関数とか正規分布関数とかいう
イメージ 5
これ、さっぱりわからん

さらに混乱するのが微妙に違う式が同じようにあること
イメージ 6
expの前のσが2乗されているのと、されていないもの
両方試して上の2乗されているほうがいい感じがしたので、上の式を使うことにして

これがガウスぼかしのカーネル作成に関係あるらしいけど
見方がさっぱりわからん
まず記号のよみと意味がわからん
分数と√ルート、2乗はわかるとして、σとexpとμ

読み 意味
σ しぐま 標準偏差、σ^2は分散になる
exp いーえっくすぴー 自然対数の底、だいたい2.7くらいの定数、
=EXP(1)で得られる、exponential(指数関数)の略
μ みゅー 平均

ガウス関数、確率密度関数ではだいたいこういう意味らしい
うーん、前に分散を使ったときはσじゃなくてアルファベットのsだったような



exp
エクセルにもそのままの名前EXP関数が用意されていて
イメージ 19
引数に1を指定すると
イメージ 20
目的の自然対数の底っていうのが得られる
よくわからんけど得られる



expの右側が指数!
expは指数関数の略だっていうから
イメージ 21
こうだと思っていたら違って、expの右側がexpの指数で

イメージ 24
こういうことらしい
expって言われたら経験値?くらいしか出てこない自分にしたら、わからんわ

エクセルやVBふうに書くと
イメージ 22
もっとエクセルらしく!
イメージ 23
長くなった





正規分布(ガウス分布)
確率密度関数(ガウス関数)を使ってできるグラフが正規分布
イメージ 7
標準偏差(σ)=1、平均(μ)=0のときは標準正規分布っていうみたいで
この曲線が重みの数値に利用されているみたい

標準正規分布
イメージ 8
σとμにそれぞれ1と0を入れて
イメージ 9
これを計算して
イメージ 10
こう、これが標準正規分布でさっきのグラフになる





ここまで見慣れない式と格闘してきたけどエクセルを使えば

エクセルのNORMDIST関数
イメージ 11
エクセルにも用意されていた!
4つ目の引数はFalseを指定すると確率密度関数の答えになるので、今回はFalseを指定


このNORMDIST関数を使って
イメージ 12
xが-5〜5までを0.2刻み、平均=0、標準偏差=1を指定して標準正規分布


イメージ 13
これでさっきの標準正規分布のグラフになる



平均(μ)
イメージ 14
平均の数値を変更するとグラフは左右に平行移動するだけで
ガウスぼかしのカーネルにとっては関係ないので
今回は平均=0で固定



標準偏差(σ)
イメージ 15
標準偏差を大きくすると、なだらかなグラフになる


3x3マスのカーネルを考えてxの値を-1,0,1の3つで
イメージ 16
小数点いっぱいだと見にくいので、一番小さい数値を1にするために、一番小さい数値で割り算して、さらに四捨五入すると
標準偏差=1のとき-1,0,1は1,2,1になった、こういうのが重みになるんだと思う


標準偏差=10にすると
イメージ 17
全部1になった


標準偏差=0.5にすると
イメージ 18
1,7,1とかなり偏った重みになった


5x5のカーネルを考えて-2から2
イメージ 25
それっぽい値が出てくる




NORMDIST関数を使わずに計算してみる
イメージ 26
μ(平均)の値はグラフの平行移動だけで、重みには関係なさそうなので0とすると
上の確率密度関数(ガウス関数)はμが取れて
イメージ 27
これでいいことになる

標準偏差=1のとき
expの前の部分をエクセルで書くと
=1/SQRT(2*PI()*1^2)
イメージ 28



expの指数の部分は
=-(x^2/(2*1^2))
-(A111^2)/(2*$A$108^2)
イメージ 29


最後にまとめて
expの前*exp^指数
イメージ 34


NORMDIST関数と比較
イメージ 35
同じ値になった!


グラフにすると
イメージ 30

標準偏差=3で、x=-10〜10
イメージ 31


ポケモンの個体値分布も正規分布?
イメージ 33
以前emuLuaで回して開始から34万件目(33分)で全部15の個体が出て、個体値ごとに集計したその時のグラフが

ポケモン緑のゼニガメの個体値合計ごとの個体数のグラフ
イメージ 32
これがあったから正規分布のグラフを見たとき思ったのが
あっポケモンだ…


ここまでは1次元
カーネルは縦横がある2次元は次回



参照したところ

正規分布 - Wikipedia


Excelによる正規分布曲線のグラフの作り方 | ブログ | 統計WEB
https://bellcurve.jp/statistics/blog/15344.html

ガウス関数の性質を5分で学ぶ - LIGHT11
http://light11.hatenadiary.com/entry/2018/05/17/225508



【数学】”exp”の意味や計算方法を文系向けに徹底解説 | ライフハック進学
https://life-hack-lab.com/zatsudan/exp/





関連記事
次回、2019/04/27
エクセルで2次のガウス関数(確率密度関数)、正規分布関数のNORMDIST ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15943071.html


2019/3/22は1年前
分割するCubeの選択、メディアンカットで減色パレット ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15425150.html
分散は減色アプリのときが初めてだった


2019/05/22
画像にノイズ付加するアプリ、一様分布乱数から正規分布乱数生成、エクセルのNORMINV関数で正規乱数 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15961286.html


前回まででいろいろなソートアルゴリズムをVBAで書くのは満足したので今回はそれらを使って計測

計測した環境は
OS:Windows 10 Home 64bit
エクセル:エクセル2007
CPU:AMD PhenomⅡ X3 720BE @3.0GHz
OS以外は2007年から使い続けている10年前のパソコン


前回までの結果
これで使っていたデータは1から10000までのランダムな数値、これの1万件だった
件数を変化させたら処理時間はどう変わるのか、10倍の10万件にしたら処理時間も10倍になるのか10^2で100倍になるのかとか

ランダム数値のデータ作成するマクロ
Sub ランダム数値1万件の配列作成()
Dim c As Long: c = 10000
Dim v() As Variant
ReDim v(c - 1)
Randomize
For i = 0 To c - 1
v(i) = CLng(c * Rnd)
Next
End Sub
だいたいこんな感じで作成


まずは時間がかかる組で千件から1万件の変化
イメージ 3
件数を10倍にしたら処理時間は約100倍になっている
Wikipediaとかの説明に
計算量はO (n^2)
とかあるnってのが件数でOが計算量ってことかなあ
これなら件数が10件なら計算量は10^2=100
100件なら100^2=10000になるから
100件の計算量は10件の100倍であっている

グラフで
イメージ 2

対数目盛で
イメージ 4
だいたい件数に比例しているかな?
シェーカーソートが遅くなっている

倍率で比較
イメージ 5
件数が増えるとシェーカーソートは遅くなるのかなあ
バブルソートは100前後で変化しないと思っていたけど
逆に効率良くなっている傾向
10万件やそれ以上は日が暮れそうなので測らない
と言うか1回計測したんだけど20分くらいたっても終わらないから諦めた
10万件を予測すると
バブルソート1で1万件は18秒だからそれの100倍で1800秒
1800/60=30分かかる
100万件なら
30*100=3000分=3000/60=50時間
50時間!
日が2回暮れる


次は速い組で比較
イメージ 6
1万、10万、100万、1000万でそれぞれ計測
速いねえ
バブルソートで50時間(予測)だったのがクイックソートなら4.5秒
それに件数が10倍になっても100倍にはなっていない

グラフで
イメージ 7
やっぱり普通のグラフだとよくわからない

対数で
イメージ 8
件数に比例しているみたいだけど
よくわからないので
倍率で比較
イメージ 9
時間がかかる組では100倍前後だったけど
速い組は10倍前後

これをグラフで
イメージ 10
コムソートはブレているなあ、データの内容に影響されやすいのかしら
マージソートはトップダウン型もボトムアップ型も変化が少ない
それ以外のシェルソート、ヒープソート、クイックソートは
件数が増えると効率良くなっている
特にクイックソートはただでさえ速いのにより効率的になっている
やっぱりクイックソートなんだなあ





同じ値がたくさんあるデータの場合
ここまでは同じ数値がほとんどないデータだった
これを1から10までのランダムな数値にしたらどうなるのか、つまり同じ値がたくさんあるデータ
データ作成するマクロ
'ランダム数値配列を作成
'cが要素数、vrに100を指定した場合は1から100の間の数値

Function RandomValue(c As Long, vr As Long) As Long()
Dim v() As Long
ReDim v(c - 1)
Randomize
For i = 0 To c - 1
v(i) = CLng(vr * Rnd)
Next
RandomValue = v
End Function
件数と数値の範囲を指定して作成

イメージ 15
同じ値がたくさんあるデータだと選択ソート以外は速くなっている

1秒以下のものは見づらいので分けたグラフ
イメージ 1
おお、シェルソートは2倍も速くなってクイックソートより速くなっている
コムソートも2割以上速くなっている





ソート率による処理時間の変化
これまでのデータは普通にバラバラなのでソート率0%
ソートが終わっているデータはソート率100%
ほとんど順番通りにソートされているのがソート率99%
この3つをそれぞれのアルゴリズムで比較

ソート率99%のデータ作成するマクロ
'99%ソート済みの配列を作成
Function Sort99Value(c As Long, vr As Long) As Long()
Dim v() As Long
ReDim v(c - 1)
Randomize
For i = 0 To c - 1
If CLng(c * Rnd) < c / 100 Then
v(i) = CLng(vr * Rnd)
Else
v(i) = i
End If
Next
Sort99Value = v
End Function
連番の中に100分の1の確率でランダムな数値を入れているだけだから、99%っていってもだいたい99%かなあってぐらいのができあがる

100%は普通の連番なので
'連番の配列を作成
Function SortedValue(c As Long) As Long()
    Dim v() As Long
    ReDim v(c - 1)
    For i = 0 To c - 1
        v(i) = i
    Next
    SortValue = v
End Function




時間がかかる組で
イメージ 11
イメージ 12
ソート済みに対してはシェーカーソートと挿入ソートは、比較処理のループは1回で終わるし交換処理は1回も発生しないから圧倒的に速い
その他もソート済みのほうがかなり速くなっているのは以外だったなあ、あんまり変わらないと思っていた、これは交換する処理コストが結構掛かるってことかな
バブルソート3は交換処理を軽くするために下準備をする処理を入れてバブルソート2より速くしたんだけど、交換が発生しない場合は逆にその下準備の分だけ遅くなってバブルソート2と逆転しているのが面白い、こうなるんだなあ



速い組
イメージ 13
イメージ 14
ヒープソートはバラバラよりソート済みのほうが遅くなっているのが面白い
こういう結果を見るまでは気づかなかったけど、なんとなく想像できる
たぶんヒープ構造を作成する部分で時間の大半を使っていて
ソート部分ではそんなにかかっていないはず
マージソートは相変わらずマイペース、バブルソートもこんなのを想像していたんだけどねえ
そしてここでもシェルソートがクイックソートより速い場面がでてきた、それでもやっぱりクイックソートは速いなあ
1万件ソートではコムソートとシェルソートはほとんど差がないから同じくらいだと思っていたけどいろいろ見たらシェルソートのほうが速いねえ



処理時間計測に使ったマクロ
Sub 計測2()
Dim r As Range
Set r = Sheets("all").Range("c185") 'タイム記入セル
Const l As Long = 5 'ループ回数指定
'実行するプロシージャ名
'全部

Const ff As String = "testBubble1,testBubble2,testBubble3,testShaker2,testShaker3,testComb2_2,testInsertion2,testShellSort2,MergeSort2,testMerge2BU,SelectSort,HeapSort3_1,testQuick4"
'速い組
'Const ff As String = "testComb2_2,testShellSort2,MergeSort2,testMerge2BU,HeapSort3_1,testQuick4"
'時間がかかる組
'Const ff As String = "testBubble1,testBubble2,testBubble3,testShaker2,testShaker3,testInsertion2,SelectSort"


Dim fName() As String
fName = Split(ff, ",")
vv = RandomValue(10000, 10) 'ランダム数値配列取得
' vv = SortValue(100000) '100%ソート済みの配列
' vv = Sort99Value(100000, 100000) '99%ソート済みの配列


Dim i As Long, j As Long
Dim v As Variant
Dim st As Single, t As Single
For j = 0 To UBound(fName)
t = 0
For i = 0 To l - 1
v = vv '配列をコピー
st = Timer
Application.Run fName(j), v 'ソート
t = t + Timer - st
Next
r.Offset(j, 0).Value = t / l '平均値をセルに記入
Next
MsgBox "計測完了"
End Sub

これとさっきのランダム数値を作成するマクロを組み合わせて計測した



イメージ 16
イメージ 17



関連記事(古い順)
エクセルVBAでバブルソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14787146.html

エクセルVBAでシェーカーソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14790656.html

エクセルVBAでコムソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14795895.html

エクセルVBAで挿入ソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14799218.html

エクセルVBAでシェルソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14801061.html

エクセルVBAでマージソートと再帰処理(再帰呼出し)...も難しいなあ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14807202.html

エクセルVBAでマージソートその2、再帰処理の必要がないボトムアップ方式で速くなった ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14810468.html

エクセルVBAでヒープソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14814563.html

エクセルVBAでクイックソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14831821.html

エクセルVBAとC++とC#とVB、それぞれのバブルソートの処理時間 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14865532.html




クイックソート
基準値を配列の中から選んで基準値以上の値を配列の右へ、基準値以下は左へ寄せてから基準値の場所で配列を二分割、この処理を分割した配列それぞれで繰り返す

'vが配列、tは並び替える範囲の先頭の添字(index)、bは最後尾の添字
Sub QuickSort4(v As Variant, t As Long, b As Long)
'ピボット、基準点、真ん中にしたのでmiddle
Dim m As Long: m = (b + t) \ 2 '演算子\は割り算の商部分
Dim p As Long: p = v(m) '基準値
Dim l As Long: l = t '左の場所
Dim r As Long: r = b '右の場所
Dim tmp As Variant
Do
'基準値以下の場所取得
Do While v(l) < p
l = l + 1
Loop
'基準値以上の場所取得
Do While v(r) > p
r = r - 1
Loop
'左右の位置を比較
If l >= r Then
'左右が同じか逆ならループ抜け
Exit Do
Else
'左右が順なら値を入れ替え
tmp = v(l)
v(l) = v(r)
v(r) = tmp
l = l + 1 '次の探索開始場所を1つ右へ
r = r - 1 '次の探索開始場所を1つ左へ
End If
Loop
'配列を分割して再帰処理
If t < l - 1 Then Call QuickSort4(v, t, l - 1) '分割の左側
If r + 1 < b Then Call QuickSort4(v, l, b) '分割の右側
End Sub
これと

Sub testQuick4(v As Variant)
Call QuickSort4(v, LBound(v), UBound(v))
End Sub
これの組み合わせで使う


配列を並べ替えるなら
Sub quickSort()
Dim v As Variant
v = Array(1, 2, 4, 2, 8, 9, 6, 8)
Call testQuick4(v)
End Sub
こう

セルの値を並べ替えるなら
Sub bubble1000()
Dim v() As Variant
Dim y As Long: y = 10000
v = WorksheetFunction.Transpose(Sheets("Sheet3").Range("a1:a" & y))
Call testQuick4(v)
Sheets("Sheet3").Range("b1:b" & y) = WorksheetFunction.Transpose(v)
End Sub
これはSheet3のA1:A10000の値を並べ替えてB1:B10000に記入する


1万件のランダム数値をソートするときの処理時間計測
OS:Windows 10
Excel2007
CPU:PhenomⅡ X3 720BE @3.0GHz
の環境で
計測するマクロはいつもの
Sub sortTestBubble2()
Dim c As Long: c = 10000
Dim v() As Variant
ReDim v(c - 1)
Randomize
For i = 0 To c - 1
v(i) = CLng(c * Rnd)
Next
Dim st As Single
st = Timer
Call testQuick4(v) 'クイックソート
MsgBox Timer - st & "秒"
End Sub

イメージ 1
評判通りの最速
1回1回計測するとタイムがばらつくので
10回連続で処理しての平均
イメージ 2
0.03秒!
これまで一番速かったコムソートとシェルソートの0.0625秒の2倍速い!

グラフで
イメージ 3

1秒以下のものだけと比較
イメージ 6



100万件をソート
イメージ 4
件数を増やしても速くて4秒台で
これもコムソート、シェルソートの2倍速い


今までのまとめ
イメージ 5





まともなクイックソートを書いたここまで来るのに1ヶ月かかった
最後の方にある
If r + 1 < b Then Call QuickSort4(v, l, b) '分割の右側
これ
分割した右側の配列を再帰処理に行くかどうかの判定の
If r + 1 < b Then
これが思いつかなくて同じ数値がある場合に無限ループになっていた
rは右側から左へ探索した基準値以下の数値の場所
bは探索範囲の最後尾の場所
たしか
If l < b Then
って書いてたのかな、これだと同じ数値がある場合には無限ループ
これを解消するのに色々遠回りな継ぎ接ぎをしてできたクイックソートが3秒もかかる代物でぜんぜんクイックじゃなかったwここでクイックソートは諦めて基本って言われるバブルソートを書いていて、Wikipediaを見ていたら他にも色々なソート方法があるのを知っていろいろ試して1ヶ月、やっとクイックなクイックソートが書けた。書けたといってもしっくりこないというかなんかイマイチ納得いかないというか相性が良くないのかなあ、1番速いんだけどねえ。速さで驚いたのはコムソートとシェルソート!バブルソートの10秒台や偽クイックソートが3秒のところに0.06秒だったからホント驚いた、それにコードにするのもすんなりできた
マージソートはクイックソートでも使っているけど再帰処理ってのが少しわかったかなあ、でも頭がこんがらがる、こんがらがるって言葉も面白いなあ、いったんマージソートも書けたぞってググっていたら再帰処理を使わないボトムアップ型ってのもあってこれで書いたら2倍も速くなったのは面白かった
ヒープソートはWikipedia見ても全くわからなかったんだよねえ、まずヒープってのが何なのかからググってなんとなくわかった気になって、でもわかって無くてを何回か繰り返してなんとか書いた感じ、だから記事もあとから見ても思い出せるようにって書いたからあんなに長くなった
Wikipediaで説明があるソート方法はだいたい書けたので、諦めていたクイックソートを書き直そうかってことで今回やっと書けたってわけ
書けたので”VBA クイックソート”でぐぐったら
1次元配列の並べ替え(バブルソート,クイックソート)|ExcelマクロVBAサンプル集
http://excel-ubara.com/excelvba5/EXCELVBA228.html
こちらの方のコードとそっくりで驚くと同時に、ああこれであっているんだという安心感
クイックソートで始まってクイックソートで終わった?感じかなあ
次回はソートする値(配列)の状態によってどんなふうに処理時間が変わるのか、変わらないのか



前回
エクセルVBAでヒープソート ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14814563.html

次回
エクセルVBAでソートアルゴリズムまとめ
エクセルVBAで、ソートアルゴリズムとデータの違いによるソート処理時間比較 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14836198.html





ついにヒープソートなんだけど、その前に選択ソート
選択ソート - 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番目以降から...これを繰り返して右端まで行ったら完了

Public Sub SelectSort2(v As Variant)
Dim i As Long, tmp As Variant, min As Variant
Dim p As Long
For i = LBound(v) To UBound(v) - 1
min = v(i)
p = i '最小値のIndex入れる用
For j = i + 1 To UBound(v)
If min > v(j) Then
min = v(j)
p = j '最小値のIndex
End If
Next
'交換
tmp = v(i)
v(i) = min
v(p) = tmp
Next
End Sub


比較する回数はバブルソートと変わらないから遅いのかなあと予想しつつ
1万件をソートの時間計測
イメージ 1
4.4804秒
バブルソートは10秒だったから2倍以上速い!
コード的にはバブルソートより簡単な気がするんだけどね、速い



ここから本番のヒープソート
ヒープソートと選択ソートに共通しているのが最小値(か最大値)を探し出して順番に並べるってところ
違うのは探す方法
ヒープソートはヒープ(ヒープ構造)ってのを使って探す
ヒープってのは日本語だと二分木らしい
けどどっちも聞いたことないよ

イメージ 2
形で言うとこういうのがヒープらしい親と子があって親のしたには子が2つある状態、
親から2つ分かれているから二分木なのかも
これだと要素を3つしか扱えないけど、子の下にさらに子を付け足していくことができる、親から見たら孫みたいね
イメージ 4
こんなふうにどんどん増やせる

番号を付ける
イメージ 3
上から下へ、左から右へ順番に番号をつけていくと都合がいい
0番の子は1番と2番ってことになって
1番の親は0番、1番の子は3番と4番
5番の親は2番ってことがわかる


中途半端な変な形
イメージ 5
要素数が増えて8個の場合はこうなる
3番の子は7番しかないけどこの形でもヒープ

順番に番号をつけられたってことは配列と同じように使えるってこと

v = Array(1, 3, 2)
っていう配列をヒープに当てはめていくと↓になる
イメージ 6
●の右上の数値は番号で
●の中は配列の値


少し戻って
ヒープには親が上で子が下にあるっていう位置関係以外にもルールがあって
親の数値>=子の数値
親は子の数値より大きいことがヒープの条件
さっきの配列をヒープに当てはめたのを見ると、0番の数値1は子の数値3と2よりも小さいのでヒープじゃないってことなる
正しいヒープに修正するには上から見ていく方法と下からがあるみたい
今回は上から見ていくので0番から
0番の子は1番と2番、どちらの数値も親より大きいけど、より大きい方と交換する
交換したところ
イメージ 7
これで正しいヒープになった

こういうのを繰り返していくと一番上が最大値になるのでこれを取り出して順番に並べると整列できるってのがヒープソートみたい


自分の親や子の番号を求める方法
親の番号を求める
親や子と比較するときはその番号が必要になる
配列の添字が0からの場合の自分の親の番号の求め方
(自分の番号-1)\2
\は割り算の商部分(小数点以下切り捨て)なので
例えば自分の番号が4なら親の番号は
(4-1)/2=1.5=1
イメージ 8
図で見ても4番の親は1番であっている

0番から14番までの親の番号一覧で確認
イメージ 9
Quotientは割り算の商を返す

配列の添字が0番から始まっていればこれで行けるんだけど0以上のときは使えなくて
(自分の番号 + 開始添字 - 1) \ 2
になる
4から始まる配列で7番の親番号は
(7+4-1)/2=5
イメージ 11
5番であっている
これも一覧で確認してみる
イメージ 10
1番から始まるときもこれでOK


今度は子の番号を求める
左の子=自分の番号*2+1
右の子=自分の番号*2+2、または左+1
自分が2番のときの子の番号は
左=2*2+1=5
右=2*2+2=6
イメージ 3
あってる
これも添字が0からの配列の場合だけなので
0以上から始まる配列のときは
左の子=自分の番号 * 2 - (開始添字 - 1)
右の子=自分の番号 * 2 - (開始添字)、または左+1
イメージ 12

イメージ 13


これで準備が整ったので配列をヒープにしていく
添字は0から始まる配列Array(5, 3, 1, 4, 6, 2)を0番から順番に

0番に5を入れて、1番に3を入れたところ
イメージ 14
親の番号は
(自分の番号-1)\2
なので
(1-1)\2=0番
親は0番
親(5)>子(3)のルールはあっているので次へ

2番に1を入れたところ
イメージ 15
2番の親の番号は(2-1)\2=0で0番
比較して
これもあっているので次へ

3番に4を追加
イメージ 16
3番の親の番号は(3-1)\2=1で1番
比較すると自分(子)のほうが大きいので交換

交換した
イメージ 17
交換した先の関係を見る
1番の親の番号は0番
比較すると今度はあっているので次へ

4番に6を追加
イメージ 18
4番の親の番号は(4-1)\2=1で1番
比較すると自分(子)のほうが大きいので交換

1番と4番を交換した
イメージ 19
交換した先の関係を見る
1番の親の番号は0番
比較すると子のほうが大きいので交換

0番と1番を交換した
イメージ 20
交換した先の関係を見る
0番の親はないので次へ

5番に次の値(2)を追加した
イメージ 21
5番の親の番号は(5-1)\2=2で2番
比較すると子のほうが大きいので交換

2番と5番を交換した
イメージ 22
交換した先の関係を見ると
2番の親の番号は0番
比較するとあっているので次へ
配列の最後の要素まで来たので終了

元の配列の並びはArray(5, 3, 1, 4, 6, 2)こうだったのが
(6, 5, 2, 3, 4, 1)となった、まだ整列(ソート)できていないけどヒープ構造にはなっている

イメージ 23
一つ一つの最小単位のヒープでは親の値>子の値というルールは守られている
この状態になると一番上(0番)には最大値が入るはずなので
最大値が判明したことになる



ここまでをVBAにしたのが

Dim i As Long, j As Long, tmp As Variant
Dim p As Long 'parent index親番号
Dim ei As Long 'end index、自分の番号(最後尾番号)
For i = 1 To UBound(v)
ei = i
p = (ei - 1) \ 2 '親番号
'親の値>子の値になるまで交換ループ

Do While v(p) < v(ei)
'比較して必要なら交換を繰り返す
tmp = v(p)
v(p) = v(ei)
v(ei) = tmp
ei = p
p = (ei - 1) \ 2 '親番号
Loop
Next



外側のループはFor〜Nextをつかっている、1から開始して配列の最後までループ、0からじゃないのは0番の親はないから
内側のループはDo While〜Loopをつかっている、追加した自分の親の値と比較して自分のほうが大きければ交換、交換した先でも親と比較して必要なら交換を繰り返して自分のほうが小さくなるまでこのループを続けるのでループ条件は
親の値 < 自分の値、v(p) < v(ei)
ループを抜けたら外側のループなので2番3番と同じように追加していく

Array(5, 3, 1, 4, 6, 2)を渡すと
イメージ 35
(5, 3, 1, 4, 6, 2)→(6, 5, 2, 3, 4, 1)
図で見ると
イメージ 36
こうなった、あっている
0番には最大値が入っているし小さな単位のヒープは
親の値 > 子の値という正しい状態



次はは整列していく作業
最大値がある0番と最後尾の5番を交換する
イメージ 24
交換した
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番のほうが大きいので交換になる

交換した
イメージ 25
次も同じように子とのヒープ状態をみて正しくなければ交換していく
図を見ればわかるけど1番と4番を入れ替えれば正しくなる
1番の子の番号は
左=1*2+1=3、右=1*2+2=4、比較して4番のほうが大きい
4番と自分を比較して4番のほうが大きいので交換

交換した
イメージ 26
4番の子は無いのでこれで修正は完了
ヒープが正しくなると0番に最大値が入っていることになるので
0番と最後尾の4番を交換する
最後尾といっても5番じゃなくて4番になるのは
5番はもう整列済みで無視するところだから

交換した
イメージ 27
これで5番に続き4番も整列済みなので以降は無視、対象外
次はまた0番を交換したせいでヒープが崩れたので
0番から修正していく

0番と1番を交換した
イメージ 28
1番の子は3番と4番になっているけど4番は整列済みなので
3番と比較、交換になる

1番と3番を交換した
イメージ 30
修正完了したので0番と最後尾を入れ替える

交換した
イメージ 29
これで3番から5番まで整列済み
また0番から修正していく

0番と1番を交換
イメージ 31
1番の子は整列済みなので修正完了
0番を最後尾と交換する

交換した
イメージ 32
今度は0番を見てもヒープは正しいので修正無しでOK
0番と最後尾を交換

交換した
イメージ 33
0番以外は整列済みで比較対象がなくなったので
全て整列済みになる

イメージ 34
ヒープソート完成!


ここまでの修正しながらの整列をVBAにすると
'上から修復
'ソート部分
Dim c1 As Long  'child1 index
Dim c2 As Long  'child2 index
Dim k As Long
For ei = UBound(v) To 1 Step -1
p = 0 'ParentIndex初期化
Do
'子同士の比較、大きい方のIndex取得
c1 = p * 2 + 1 '左の子Index
c2 = c1 + 1 '右の子
If c2 > ei Then '右Indexが最後尾を超えていたら
k = c1 '自動的に左Indexを採用
ElseIf v(c1) > v(c2) Then '子同士の比較
k = c1
Else
k = c2
End If

'自分(親)と大きい方の子を比較
If v(k) > v(p) Then
'子が大きければ交換
tmp = v(k)
v(k) = v(p)
v(p) = tmp
p = k '自分のIndexを入れ替えた先のIndexに変更
Else
'子が小さければ修復完了
Exit Do
End If
'自分の左の子がなくなるまでループ
Loop While p * 2 + 1 < ei

'先頭(最大値)とソート範囲の最後尾を入れ替える
tmp = v(0)
v(0) = v(ei)
v(ei) = tmp
Next

ループは2つ、外側のループは要素の総数から1づつ減らしていって1になるまでループ、減らしていっているのは整列済みがどんどん増えていくからその分を除くため

内側のループはDo〜Loopでループ条件の
p * 2 + 1 < eiは
左の子の番号 < 最後尾番号
つまり自分の子がなくなるまでループ

内側ループの前半は子同士の比較をして大きい方の番号を取得
後半はその大きい方の子と自分を比較して子が小さければ修復完了なので内側ループ抜けして、大きければ交換してループを続ける

内側ループが終わったら最大値と最後尾を交換して外側ループ
最後尾番号が1になったらソート完了

これでソート部分もできたのでさっきの配列をヒープにしていくのと合わせるとヒープソートの完成↓

'添字が0から始まる配列だけに対応したヒープソート
Public Function HeapSort3_1(v As Variant) As Variant
Dim i As Long, j As Long, tmp As Variant
Dim p As Long 'parent index親番号
Dim ei As Long 'end index、最後尾番号

'ヒープ構造作成
'元の配列をそのまま使う
For i = 1 To UBound(v)
ei = i
p = (ei - 1) \ 2 '親番号
'親の値>子の値になるまで交換ループ
Do While v(p) < v(ei)
'比較して必要なら交換を繰り返す
tmp = v(p)
v(p) = v(ei)
v(ei) = tmp
ei = p
p = (ei - 1) \ 2 '親番号
Loop
Next

'上から修復ソート部分
Dim c1 As Long  'child1 index
Dim c2 As Long  'child2 index
Dim k As Long
For ei = UBound(v) To 1 Step -1
p = 0 'ParentIndex初期化
Do
'子同士の比較、大きい方のIndex取得
c1 = p * 2 + 1 '左の子Index
c2 = c1 + 1 '右の子
If c2 > ei Then '右Indexが最後尾を超えていたら
k = c1 '自動的に左Indexを採用
ElseIf v(c1) > v(c2) Then '子同士の比較
k = c1
Else
k = c2
End If
'自分(親)と大きい方の子を比較、子が大きければ交換
If v(k) > v(p) Then
tmp = v(k)
v(k) = v(p)
v(p) = tmp
p = k '自分のIndexを入れ替えた先のIndexに変更
Else
Exit Do
End If
Loop While p * 2 + 1 < ei
'先頭(最大値)とソート範囲の最後尾を入れ替える
tmp = v(0)
v(0) = v(ei)
v(ei) = tmp
Next

HeapSort3_1 = v
End Function



やっとできたヒープソートはどれくらい速いのか
ランダム数値の1万件ソート
イメージ 37
5回計測、平均すると0.085秒
速い!


今までのまとめ
イメージ 38
だんだん長くなってきた

グラフで
イメージ 39
コムソートとシェルソートには及ばずだけど
前回のマージソートよりは速い結果になった
バブル、シェーカーの遅い組
挿入、選択の中間組
コム、シェル、マージ、ヒープの速い組
3つに分かれる感じかなあ


100万件ソート
イメージ 40
12.6秒
あれ、遅い?


イメージ 41
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

マージソートも難しかったけどヒープソートはさらに時間がかかった、4日くらいかかってやっとまともなのができたんだったかなあ
イメージ 42
最初できたのは1万件をソートで10分(815秒)以上もかかるものだったw
そこから一応これくらいなのかなっていう1秒台まで縮めたあとは一旦諦めてマージソートを書いていたみたいで、その後に何か思いついて0.1秒になって今回の0.085秒までは1週間くらいかかったのかなあ、それでも理解できていないから本当はもっと速いのかも、でももうヒープソートはお腹いっぱい
残るは最速と言われているクイックソートなんだけど、これが全然わかんなくて3秒もかかるものしか書けてない


'開始添字が0以上でもOKのヒープソート
さっきのヒープソートは添字は0からしか対応していないけど、エクセルでは1から始まる配列を扱うことが多いので書いた
ほとんど一緒で違うのはループの条件とループの開始位置

Public Function HeapSort4_1(v As Variant) As Variant
Dim i As Long, j As Long, tmp As Variant
Dim c As Long
Dim min As Long: min = LBound(v)
Dim ei As Long 'end index
'ヒープ構造作成
'元の配列をそのまま使う
For i = min + 1 To UBound(v)
ei = i
p = (ei + min - 1) \ 2
Do While p > min And v(p) < v(ei)
tmp = v(p)
v(p) = v(ei)
v(ei) = tmp
ei = p
p = (ei + min - 1) \ 2
Loop
Next

'上から修復
'ソート部分
Dim c1 As Long  'child1 index
Dim c2 As Long  'child2 index
Dim k As Long
ei = UBound(v)
For ei = UBound(v) To min + 1 Step -1
p = min 'ParentIndex初期化

Do
'子同士の比較、大きい方のIndex取得
c1 = p * 2 - (min - 1) '左の子Index
c2 = c1 + 1 '右の子
If c2 > ei Then '右Indexが最後尾を超えていたら
k = c1  '自動的に左Indexを採用
ElseIf v(c1) > v(c2) Then '子同士の比較
k = c1
Else
k = c2
End If
'自分(親)と大きい方の子を比較、子が大きければ交換
If v(k) > v(p) Then
tmp = v(k)
v(k) = v(p)
v(p) = tmp
p = k '自分のIndexを入れ替えた先のIndexに変更

Else
Exit Do
End If
Loop While p * 2 - (min - 1) < ei

'先頭(最大値)とソート範囲の最後尾を入れ替える
tmp = v(min)
v(min) = v(ei)
v(ei) = tmp
Next
HeapSort4_1 = v
End Function

これを使ってSheet3のA1からA10000をソートしてB列に貼り付けるマクロ
Sub bubble1000()
Dim v() As Variant
Dim y As Long: y = 10000
v = WorksheetFunction.Transpose(Sheets("Sheet3").Range("a1:a" & y))
v = HeapSort4_1(v)
Sheets("Sheet3").Range("b1:b" & y) = WorksheetFunction.Transpose(v)
End Sub


次回
エクセル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でソートアルゴリズムまとめ
エクセルVBAで、ソートアルゴリズムとデータの違いによるソート処理時間比較 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14836198.html






全12ページ

[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]

[ 次のページ ]

わてん@午後
わてん@午後
男性 / 非公開
人気度
Yahoo!ブログヘルプ - ブログ人気度について

スマートフォンで見る

モバイル版Yahoo!ブログにアクセス!

スマートフォン版Yahoo!ブログにアクセス!

1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

最新のコメント最新のコメント

すべて表示

Yahoo!からのお知らせ

検索 検索

よしもとブログランキング

もっと見る
本文はここまでですこのページの先頭へ

[PR]お得情報

お肉、魚介、お米、おせちまで
おすすめ特産品がランキングで選べる
ふるさと納税サイト『さとふる』
ふるさと納税サイト『さとふる』
11/30まで5周年記念キャンペーン中!
Amazonギフト券1000円分当たる!
数量限定!イオンおまとめ企画
「無料お試しクーポン」か
「値引きクーポン」が必ず当たる!

その他のキャンペーン

みんなの更新記事