Go言語と Ruby で 8 queens 問題

Go で「8 queens 問題」を解いてみました。実際は n queens が解けます。

すべての解を出力します。num = 8 の場合、解は 92通りあります。こんな感じ。

------------------
n = 1
@.......
....@...
.......@
.....@..
..@.....
......@.
.@......
...@....
------------------
n = 2
@.......
.....@..
.......@
..@.....
......@.
...@....
.@......
....@...
------------------
(以下略)

 
コードはこんな感じです。
eight_queen.go

package main
import "fmt"
import "strings"

const num = 8
var count = 0

//出力
func putout(field []int) {
    count++
    fmt.Println("------------------")
    fmt.Printf("n = %d\n", count)
    st := strings.Repeat(".", num - 1)
    for _, queen := range field {
        fmt.Println(st[0: queen] + "@" + st[0: num - queen - 1])
    }
}

//ここまでの field で女王の置けるマス(空白)を探す(1なら置けない)
func get_space(field []int) [num]int {
    result := [num]int{}
    l := len(field)
    for i, queen := range field {
        result[queen] = 1
        dst := l - i
        if a := queen - dst; a >= 0  {result[a] = 1}
        if a := queen + dst; a < num {result[a] = 1}
    }
    return result
}

//メインの深さ優先探索
func solve(field []int) {
    if len(field) == num {
        putout(field)
    } else {
        for i, space := range get_space(field) {
            if space == 0 { solve(append(field, i)) }
        }
    }
}

func main () {
    solve([]int{})
}

実質的に solve() と get_space() の 2つのメソッドだけでシンプルに書けました。putout() は出力用です。たぶん、これはたいていの言語にそのまま移植できると思います。
 
結構うまく書けたのではないでしょうか。再帰的に深さ優先探索で解いています。

実行時間は num = 8 の場合ほぼ一瞬(0.01秒くらい)です。num = 15 の場合、最終的な解の個数だけ出力するように改変して(コード)実行すると

$ time ./eight_queen
2279184

real	0m17.135s
user	0m17.332s
sys	0m0.040s

となります。num = 17 で

$ time ./eight_queen
95815104

real	13m38.920s
user	13m42.884s
sys	0m1.228s

くらいが限界でしょうか。

Go で書くのは好きですね。コードの見た目がいいです。

※参考
これまでの試み。今回は以前とはだいぶちがったロジックで求めています。
エイト・クイーン(8 queen)問題を Ruby で解いてみる - Camera Obscura
ふたたび Ruby で 8 queen(関数型プログラミングっぽく) - Camera Obscura
Swift で 8queen 問題を解く - Camera Obscura
 

ちなみに Ruby だとこんな風に移植できますか。
eight_queen_another.rb

#ここまでの field で女王の置けるマス(空白)を探す(falseなら置けない)
def get_space(field)
  result = Array.new(N, true)
  l = field.size
  field.each_with_index do |queen, i|
    result[queen] = false
    dst = l - i
    result[queen - dst] = false if queen - dst >= 0
    result[queen + dst] = false if queen + dst <  N
  end
  result
end

#メインの深さ優先探索および出力
def solve(field)
  if field.size == N
    @count += 1
    puts "------------------"
    puts "n = #{@count}"
    field.each {|queen| puts ("." * (N - 1)).insert(queen, "@")}
  else
    get_space(field).each_with_index {|space, i| solve(field + [i]) if space}
  end
end

N = 8
@count = 0
solve([])

「あなたのセッションは10秒以上続きませんでした」

Linux Mint 18.3 を起動しようとしたところ、以下のような表示が出て立ち上がらない。

あなたのセッションは10秒以上続きませんでした。もしあなた自身がログアウトしていない場合、インストールに問題があるか、ディスクの空き容量が足りないかもしれません。フェイルセーフなセッションからログインし、この問題を解決できるかどうか確認してください。

ちなみにこれはここからコピペしたものである。~/.xsession-errors ファイルの内容はリンク先とはちがい、fcitx 関係(追記:これはたぶんまちがい。正しくは initctl だったらしい)のものだった。原因がまったくわからなかったので、[Ctrl] + [Alt] + [F1] で tty に入り、timeshift を実行。

$ sudo timeshift --list
$ sudo timeshift --restore

画面の指示にしたがってリカバリーする。途中の GRUB2 の再インストールは実行するのが recommend だったのにしなかったので、たぶんそのせいで「reboot..」が出てもまったくリブートしない。これは失敗。リカバリーの途中では絶対に中断してはいけないが、どうも終了しているようだったので、いつもの [Alt] + [PrtSc] + [R], [S], [E], [I], [U], [B] で初期化したところ何とか再起動、うまく立ち上がった(ホッ)。

timeshift で救われたのは既に 2回目である。僕のメインマシンは Linux とあまり相性がよくないので、Mint 18.3 から導入された timeshift は簡単にリカバリーできて初心者にはすごく助かる。マシンとの相性がよくない方で、実力充分どんな不具合でも welcome とはいかない方は是非導入をお勧めする。Ubuntu 系統なら導入できる筈。

ちなみに原因はまったく不明。直前のセッションは端末から poweroff で終了したのだが、これはこれまで何度もやっていておかしいとはあまり思えない。あるとすれば直前のシステムのアップデートだが、それだったのか。とりあえずシステム更新は時間をおいてみることにする。


※追記
これを書いたところ検索で来られる方が少なくない。どうも自分の PC だけではないと思う。システムの更新後また不具合が再発したので、まず確実に apt で更新したパッケージがおかしい。はっきりとは言えないけれど、たぶん「tiff」があやしい(バージョンは 4.0.6.1ubuntu4)。まだ不具合が出ていない方は、apt でのアップデートは控えた方がよいかもしれない。具体的な解決策を提示できなくて、わざわざ来られた方には申し訳ないです。


追加。検索していておかしいパッケージがわかりました。virtualbox でバージョンは 5.1.34-dfsg-0ubuntu1.16.04.2 です。これをインストールするとおかしくなるようです。問題はここで議論されています。
https://forums.linuxmint.com/viewtopic.php?f=47&t=266431
とりあえず

$ sudo apt-get remove virtualbox*

あるいは

$ sudo apt-get remove virtualbox-guest*

で関連パッケージを削除するといいみたいな回答があがっていますが、未確認なので保証できません。実行は自己責任でお願いします。

不具合が出るのはいまのところ Linux Mint でしか確認していません。


追加。いちおう下が最新の解決策のようです(3/27 AM10:56)。自分では確認していないので実行は自己責任でお願いします。
https://forums.linuxmint.com/viewtopic.php?f=47&t=266431&start=40#p1449369
必要部分だけ抜き出しておきます。

  • [Ctrl] + [Alt] + [F1] でコンソールに入る
  • テキストモードでログインする
  • 下のコマンドで virtualbox guest を取り除く

sudo apt-get purge virtualbox-guest-dkms virtualbox-guest-utils virtualbox-guest-x11

  • PC を再起動する
https://forums.linuxmint.com/viewtopic.php?f=47&t=266431&start=40#p1449369

 

追記。問題が認識されたようで、いま virtualbox はアップデートマネージャのアップデートの対象から外れているようです。上の英語版 Linux Mint フォーラムではまだ議論が続いているようなので、参考にされる方はよくお確かめ下さい。(3/27 PM06:08)


追記。修正されたようです。自分の環境では新たなアップデートは問題ありませんでした。(3/28 PM00:12)

Ruby でカレンダーを出力してみた

特定の年と月を指定して、カレンダーを出力するプログラムを書いてみました。

標準添付ライブラリの Date クラスを使ってはつまらないので、自力で計算しました。
calender.rb

month_table = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

#うるう年か?
is_uruu = ->(year) {
  (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}

#西暦1年1月1日から何日目か(すべてグレゴリオ暦で計算)
days = ->(year, month, day) {
  uruu = ->(y) {
    y / 4 - y / 100 + y / 400
  }
  month_days = ->{
    month_table[0, month].inject(&:+) + (is_uruu.(year) && month > 2 ? 1 : 0)
  }
  y1 = year - 1
  y1 * 365 + uruu.(y1) + month_days.() + day - 1
}

#曜日の計算
week_number = ->(year, month, day) {
  (days.(year, month, day) + 1) % 7
}

#カレンダーの出力
Calender = ->(year, month) {
  gen = ->(from, to) {
    (from..to).map {|i| sprintf("%2d  ", i)}.join
  }
  putout = ->(i) {
    last = month_table[month]
    last += 1 if is_uruu.(year) && month == 2
    while i + 6 <= last
      puts gen.(i, i + 6)
      i += 7
    end
    st = gen.(i, last)
    puts st unless st.empty?
  }
  puts "#{year}/#{month}".center(27)
  puts "sun mon tue wed thu fri sat"
  w = week_number.(year, month, 1)
  puts "    " * w + gen.(1, 7 - w)
  putout.(8 - w)
}

実行例。

$ pry
[1] pry(main)> require "./calender"
=> true
[2] pry(main)> Calender[2018, 3]
          2018/3           
sun mon tue wed thu fri sat
                 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  31  
=> nil
[3] pry(main)> Calender[1989, 1]
          1989/1           
sun mon tue wed thu fri sat
 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  31  
=> nil
[4] pry(main)> Calender[1968, 7]
          1968/7           
sun mon tue wed thu fri sat
     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  31  
=> nil
[5] pry(main)> Calender[2018, 2]
          2018/2           
sun mon tue wed thu fri sat
                 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  
=> nil

 
なお、days.() 関数は西暦1年1月1日から何日目かを計算しますが、すべてグレゴリオ暦で計算しています。Ruby の Date クラスでは、それ以前のユリウス暦の期間を補正しているらしいので、こことはちがった値を出力します。なので厳密にいえば、このカレンダーはグレゴリオ暦に切り替わって以降の分しか正しい出力ではないことになります。なお、未来のカレンダーとしては正しいものを出力する筈です。

week_number.(year, month, day) はその日の曜日を出力します。日曜日は 0、月曜日は 1, ..土曜日は 6 という具合です。ソースの最後に

if __FILE__ == $0
  week_table = %W(日 月 火 水 木 金 土)
  puts week_table[week_number.(*ARGV.map(&:to_i))] + "曜日"
end

を加えると、

$ ruby calender.rb 2018 3 27
火曜日

という風にその日の曜日を出力することができます。


こんなのもどうぞ。
obelisk.hatenablog.com

素因数分解(Ruby)

素因数分解は結局素数で順に割っていくしかないのだけれど、素数をわざわざ求めてというのは却って大変ですね。

うまいやり方としては、2 および 3 以上の奇数で割るという方法があります。これは多少のムダは出るけど、全部の数で割るより効率的ですね。さらにうまいやり方があって、2, 3 以上は 5, 7, 11, 13, 17, 19, 23, 25, 29 .... と、ステップ幅を 2 と 4 の交代にして割っていくという方法があります。かしこいですね。これは例えば Ruby でこんなふうに書けます。
prime_factorize.rb

def prime_factorize(n)
  if n <= 1 or !n.class.ancestors.include?(Integer)
    raise ArgumentError, "#{n} incorrect."
  end
  result = {}
  divide = ->(i) {
    while (n % i).zero?
      result[i] = result.has_key?(i) ? result[i] + 1 : 1
      n /= i
    end
  }
  divide[2]
  divide[3]
  k, step = 5, 2
  guard = Math.sqrt(n).to_i
  while n != 1
    divide[k]
    k += step
    return result if k > guard    #高速化
    step = 6 - step
  end
  result
end

if __FILE__ == $0
  p prime_factorize(15141523320)    #=>{2=>3, 3=>3, 5=>1, 7=>2, 11=>1, 19=>1, 37=>2}
end

結果はハッシュで返ります。

なお、素因数分解Ruby では標準添付ライブラリにあるので、実際はそれを使えばよいですね。ちなみに例えば 69316536495422 の素因数分解をやると、上のプログラムではフリーズしますが(笑)、ライブラリのメソッドだと苦もなく瞬時に素因数分解します。
※追記:2行付け加えただけで劇的に高速化しました。これだと 69316536495422 の素因数分解でも一瞬です。(4/29))

 

C言語による最新アルゴリズム事典 (ソフトウェアテクノロジー)

C言語による最新アルゴリズム事典 (ソフトウェアテクノロジー)

L-system で描いた再帰曲線の例

obelisk.hatenablog.com
前回のエントリで Ruby で実装してみた L-system を使って、実際に再帰曲線を描いてみます。Gem 'kaki-lsystem' が必要です。


C曲線。
20180323002343
コード。

require 'kaki/lsystem'

l = Lsystem.new(600, 500, "C curve")
l.move(-130, 100)
l.prologue do
  clear(color(0x2b5, 0x60ff, 0x5d89))
  color(0xfc9e, 0xe0aa, 0x7ac7)
end
l.set("-") {left(45)}
l.set("+") {right(45)}
l.set("F") {forward(8)}
l.init("F")
l.rule("F", "+F--F+")
l.draw(10)

以下、require 'kaki/lsystem' は書きません。


コッホ・アイランド。
20180322043252
コード。

l = Lsystem.new(600, 600, "Koch island")
l.move(-150, -150)
l.prologue do
  clear(color(0xffff, 0xf600, 0xf540))
  color(0x28a5, 0xa3c8, 0xca7)
end
l.set("-") {left(90)}
l.set("+") {right(90)}
l.set("F") {forward(8)}
l.init("F-F-F-F")
l.rule("F", "F+FF-FF-F-F+F+FF-F-F+F+FF+FF-F")
l.draw(2)

 

ペンローズ・タイル
20180322042852
コード。

l = Lsystem.new(600, 600, "Penrose Tiling")
l.prologue do
  clear(color(0xe013, 0xfee3, 0xfee3))
  color(0xffff, 0x5feb, 0xe51f)
end
l.set("-") {left(36)}
l.set("+") {right(36)}
walk = proc {forward(10)}
l.set("M", &walk)
l.set("N", &walk)
l.set("O", &walk)
l.set("P", &walk)
l.set("A", &walk)
l.init("[N]++[N]++[N]++[N]++[N]")
l.rule("M", "OA++PA----NA[-OA----MA]++")
l.rule("N", "+OA--PA[---MA--NA]+")
l.rule("O", "-MA++NA[+++OA++PA]-")
l.rule("P", "--OA++++MA[+PA++++NA]--NA")
l.rule("A", "")
l.draw(5)

 

Sierpinski arrowhead。続けると「シェルピンスキーのギャスケット」になります。
20180324150434
コード。

n = 6
length = n.zero? ? 500 : 500.0 / 2 ** n

l = Lsystem.new(520, 500, "Sierpinski arrowhead")
l.move(-250, -240)
l.prologue do
  clear(color(0xffff, 0xef5b, 0xf677))
  color(0x12de, 0x5320, 0xb4a)
end
l.set("-") {left(60)}
l.set("+") {right(60)}
l.set("L") {}
l.set("R") {}
l.set("F") {forward(length)}
l.init("LF")
l.rule("L", "-RF+LF+RF-")
l.rule("R", "+LF-RF-LF+")
l.rule("F", "")
l.draw(n)

 
 
20180324150916
コード。

l = Lsystem.new(500, 500)
l.move(50, -230)
l.prologue do
  clear(color(0xd467, 0xfbab, 0xd0c7))
  color(0xe713, 0x5742, 0xff23)
end
l.set("-") {left(90)}
l.set("+") {right(90)}
l.set("F") {forward(10)}
l.init("F-F-F-F")
l.rule("F", "FF-F-F-F-F-F+F")
l.draw(3)

 
 
草っぽいの。
20180323002349
コード。

l = Lsystem.new(500, 500)
l.move(-150, -240)
l.prologue do
  clear(color(0xf7d3, 0xffff, 0xc99b))
  color(0x30e3, 0x8636, 0x10d2)
end
l.dir = Vector[0, 1]
l.set("-") {left(10)}
l.set("+") {right(15)}
l.set("F") {forward(13)}
l.set("X") {}
l.set("Z") {}
l.init("X")
l.rule("F", "FX[FX[+XF]]")
l.rule("X", "FF[+XZ++X-F[+ZX]][-X++F-X]")
l.rule("Z", "[+F-X-F][++ZX]")
l.draw(4)

 

ペアノ・ゴスペル曲線。
20180325031914
コード。

l = Lsystem.new(600, 550, "Peano-Gosper curve")
l.move(80, 250)
l.prologue do
  clear(color(0x5968, 0x5968, 0x5968))
  color(0x8024, 0xffff, 0x653)
end
l.set("-") {left(60)}
l.set("+") {right(60)}
l.set("F") {forward(9)}
l.set("X") {}
l.set("Y") {}
l.init("X")
l.rule("X", "X+YF++YF-FX--FXFX-YF+")
l.rule("Y", "-FX+YFYF++YF+FX--FX-Y")
l.draw(4)

再帰曲線を描く言語「L-system」を Ruby で実装した

L-system って何でしょうか。Wikipedia にはこうあります。

L-system(エルシステム、Lindenmayer system)は形式文法の一種で、植物の成長プロセスを初めとした様々な自然物の構造を記述・表現できるアルゴリズムである。自然物の他にも、反復関数系(Iterated Function System; IFS)のようないわゆる自己相似図形やフラクタル図形を生成する場合にも用いられる。

https://ja.wikipedia.org/wiki/L-system

 

つまりは、再帰曲線を描く言語ですね。例としてまず「コッホ曲線」を描いてみましょう。
まず、前に一定の長さ進むコマンドを「F」とします。で、開始手続きも「F」としましょう。これだけで
20180322132620
と直線が一本引かれます。
つぎに、再帰的な「置き換え」の規則を定めます。それを「F→F-F++F-F」とします。これは開始手続きの「F」を「F-F++F-F」で置き換えるということになります。「-」は左60度、「+」は右60度の回転とします。よって、新たな手続きも「F-F++F-F」となります。これを図示すると
20180322132618
となります。
置き換えを続けましょう。今度は前の手続き「F++F-F」の F をすべて F++F-F に置き換えます。すると「F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F」となります(ややこしいですね)。これは
20180322132615
となります。
もう一度置き換えてみます。今度は「F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F-F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F-F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F」ですよ。これは
20180322132613
となりますね。まさしく再帰的な「コッホ曲線」が描けています。


さて、これはこんなコードで描けます。Gem化しているので、Gem 'kaki-lsystem' をインストールして下さい。

require 'kaki/lsystem'

n = 3
length = 500.0 / 3 ** n

l = Lsystem.new(550, 200)
l.move(-250, -70)
l.set("-") {left(60)}
l.set("+") {right(60)}
l.set("F") {forward(length)}
l.init("F")
l.rule("F", "F-F++F-F")
l.draw(n)

init("F") は開始手続きの指定、rule("F", "F-F++F-F") は置き換えの規則の指定です。draw(3) で変換を 3回施したものを描画します。

set("-") {left(60)} はコマンド「-」が何をするのか、ブロック内に記述します。つまり left(60) は左60度回転ということですね。forward(20) ならまっすぐ 20 進むということです(ちなみに forward(20, false) なら、描画せずに 20 進みます)。このブロック内は、じつは Gem 'oekaki' のタートルグラフィックスの機能を使っているので、そのメソッドを書きます。よく使うのは left(deg), right(deg), forward(length, draw=true) あたりでしょうか。これについては

などを参考にして下さい。なお、set() は使用したすべてのコマンドについて設定して下さい。何もしない場合でも空のブロックが必要です。また、rule() で変換規則が指定されていないコマンドは、そのまま持ち越されます。


置き換えるコマンドを二種類以上にすることもできます。例えば

require 'kaki/lsystem'

l = Lsystem.new(400, 350, "Dragon curve")
l.move(-50, -50)
l.prologue do
  clear(color(0xf7d3, 0xffff, 0xc99b))    #背景色
  color(0x143f, 0x832d, 0xe783)           #ペンの色
end
l.set("-") {left(90)}
l.set("+") {right(90)}
l.set("A") {forward(20)}
l.set("B") {forward(20)}
l.init("A")
l.rule("A", "A+B+")
l.rule("B", "-A-B")
l.draw(7)

では、開始手続きが A で、置き換えの規則は「A→A+B+」「B→-A-B」と二種類になっています。これを実行すると
20180322141013
となります。いわゆる「ドラゴン曲線」ですね。他のメソッドも簡単に説明しておきましょう。move(x, y) はペンの開始位置を指定します。デフォルトは (0, 0) で、これはキャンバスの中心です。なお、数学のグラフと同じで y方向は上がプラスの向きです。prologue {...} はブロック内で初期処理をします。背景色や色の指定をすることが多いです。なお、ここでは残念ながら left(), right() が使えない(というか2度呼ばれてしまうので正確に指定できません)ので、ペンの最初の方向はアクセサーメソッド dir= で指定することになります(あとで説明します)。


コマンド「[」「]」でスタックのプッシュ、ポップを表します。つまり [ で状態を保存し、 ] で元の状態に復帰します。例えば

require 'kaki/lsystem'

l = Lsystem.new(400, 640)
l.move(50, -310)
l.prologue {color(0x28a5, 0xa3c8, 0xca7)}
l.dir = Vector[0, 1]
l.set("-") {left(15)}
l.set("+") {right(15)}
l.set("F") {forward(10)}
l.init("F")
l.rule("F", "F[+F-F-F]F[--F+F+F]")
l.draw(4)

こんな風です。dir= で開始時の方向をベクトルで指定しています。ただし、ベクトルは単位ベクトル(長さが 1 )になるようにして下さい。この結果は
20180322143459 20180322143456 20180322142514
となります。左からそれぞれ 1, 2, 4 回の繰り返しになります。


ブロックはクロージャなので、変数を使ったサンプル。「ヒルベルト曲線」の色を描画しながら替えていきます。

require 'kaki/lsystem'

n, f = 0, 0
col = [[0xffff, 0, 0], [0, 0xffff, 0]]

l = Lsystem.new(500, 500, "Hilbert curve")
l.move(-230, 230)
l.set("+") {left(90)}
l.set("-") {right(90)}
l.set("A") {}
l.set("B") {}
l.set("F") do
  if n.zero?
    f = (f + 1) % 2
    color(*col[f])
  end
  forward(15)
  n = (n + 1) % 16
end
l.init("A")
l.rule("A", "-BF+AFA+FB-")
l.rule("B", "+AF-BFB-FA+")
l.draw(5)

結果。
20180322144224
 

Gem 'kaki-lsystem' のソースコードは Gist にあります。この Gem は内部で Gem 'oekaki' を使っています。
https://gist.github.com/obelisk68/1c3d278cb5fe2778a11ba719d44af743


※参考にしたページ(ありがとうございます!)
L-system
L-system入門 - 人工知能に関する断創録
HiiragiCompany -L-System Tips-
L-systemを利用した植物の描画 | Makoto Hirahara
 
 
※L-system の描画例
obelisk.hatenablog.com

ヒープソート(C言語、Go言語)

(追記)ヒープを作るのに、何だかふつうとはちがう(オリジナルの?)アルゴリズムを採用してしまったようですね。これでもちゃんと動作しますが、ふつうの方法よりも多少効率が悪いようです。最後にふつうの実装も載せます。



 
ヒープソートですが、まず「(二分木)ヒープ」を作ります。(ここでいうヒープは「ヒープ領域」とは関係ありません。)

ヒープは二分木構造をもっていて、どこを取っても2つの(あるいは最後だけ1つの)子の値よりも、親の値の方が大きいようになっているものです。ヒープはいろいろな使い道があるそうです。まずはヒープを作ってみます。結構むずかしいです。

  1. 親のインデックスを parent とすると、左側の子のインデックスは parent * 2 + 1 になります。
  2. 子が存在しない場合は、親のインデックスをひとつ減らして最初から同じことをします。
  3. 子が存在するばあいは、左の子と右の子で値の大きい方を選び、それを larger とします。
    • 親の方が larger 以上ならば、親のインデックスをひとつ減らして最初から同じことをします。
    • そうでなければ 親と larger の位置を交換します。そして、その降りてきた親を新しい親として、最初から同じことをします。

終了条件は、親のインデックスが負になった場合です。開始時の親は、配列の最後のその親になります。これのインデックスは、配列の長さを len とすると、(len - 2) / 2 で表されます。

これを C で表現してみましょう。

void build_heap(int ar[], int last) {
    int child;
    int parent = (last - 1) / 2;
    
    while (parent >= 0) {
        child = parent * 2 + 1;
        if (child > last) {
            parent--;
            continue;
        }
        if (child != last) && ar[child + 1] > ar[child]) child++;
        if (ar[parent] >= ar[child]) {
            parent--;
            continue;
        }
        swap(ar, parent, child);
        parent = child;
    }
}

swap() は値の交換、last は配列の右端のインデックスです。これでヒープを作ることができました。
例えば [4, 1, 6, 2, 9, 7, 3, 8] からヒープを作ると、[9, 8, 7, 4, 1, 6, 3, 2] となります。こういう二分木ですね。

9 -- 8 -- 4 -- 2
       -- 1
  -- 7 -- 6
       -- 3

9 の子が「8, 7」、8 の子が「4, 1」、7 の子が「6, 3」、4 の子が「2」ということです。どこを見ても親の方が大きくなっています。

なお、これは上の方の値が大きくなるので、正確には「最大ヒープ」というものです。上の方ほど小さいヒープは、「最小ヒープ」と呼ばれます。
 

では、これを使ってソートしてみましょう。

  1. まずヒープを作ります。
  2. ヒープの頭(配列の先頭)が最大値なので、これと配列の最後を入れ替えます。
  3. 配列の最後(最大値)を除いたものを再帰的にソートして、除かれたものを最後に付け加えます。

これがヒープソートです。C で表現するとこんな感じです。

void heap_sort(int ar[], int len) {
    if (len <= 1) return;
    build_heap(ar, len - 1);
    swap(ar, 0, len - 1);
    heap_sort(ar, len - 1);
}

 
全体のコードとしてはこうなります。
heap_sort.c

#include <stdio.h>

void swap(int ar[], int a, int b) {
    int tmp = ar[a];
    ar[a] = ar[b];
    ar[b] = tmp;
}

void build_heap(int ar[], int last) {
    int child;
    int parent = (last - 1) / 2;
    
    while (parent >= 0) {
        child = parent * 2 + 1;
        if (child > last) {
            parent--;
            continue;
        }
        if (child != last && ar[child + 1] > ar[child]) child++;
        if (ar[parent] >= ar[child]) {
            parent--;
            continue;
        }
        swap(ar, parent, child);
        parent = child;
    }
}

void heap_sort(int ar[], int len) {
    if (len <= 1) return;
    build_heap(ar, len - 1);
    swap(ar, 0, len - 1);
    heap_sort(ar, len - 1);
}

int main() {
    int ar[] = {4, 1, 6, 2, 9, 7, 3, 8};
    int len = sizeof ar / sizeof(int);
    
    heap_sort(ar, len);
    
    printf("[");
    for (int i = 0; i < len; i++) {
        printf("%d, ", ar[i]);
    }
    printf("\b\b]\n");
    
    return 0;
}

 

Go言語版。C の場合とほぼ同じです。
heap_sort.go

package main
import "fmt"

func build_heap(ar []int) []int {
    last := len(ar) - 1
    parent := (last - 1) / 2
    for parent >= 0 {
        child := parent * 2 + 1
        if child > last {
            parent--
            continue
        }
        if child != last && ar[child + 1] > ar[child] {child++}
        if ar[parent] >= ar[child] {
            parent--
            continue
        }
        ar[parent], ar[child] = ar[child], ar[parent]
        parent = child
    }
    return ar
}

func heap_sort(ar []int) []int {
    len := len(ar)
    if len <= 1 {return ar}
    ar = build_heap(ar)
    ar[0], ar[len - 1] = ar[len - 1], ar[0]
    return append(heap_sort(ar[:len - 1]), ar[len - 1])
}

func main() {
    ar := heap_sort([]int{4, 1, 6, 2, 9, 7, 3, 8})
    fmt.Println(ar)
}

 

ふつうの実装

C言語
heap_sort1.c

#include <stdio.h>

void heapify(int ar[], int parent, int last) {
    int child, v;
    
    v = ar[parent];
    while (1) {
        child = parent * 2 + 1;
        if (child > last) break;
        if (child != last && ar[child + 1] > ar[child]) child++;
        if (v >= ar[child]) break;
        ar[parent] = ar[child];
        parent = child;
    }
    ar[parent] = v;
}

void build_heap(int ar[], int last) {
    for (int i = (last - 1) / 2; i >= 0; i--)
        heapify(ar, i, last);
}

void heap_sort(int ar[], int len) {
    if (len <= 1) return;
    build_heap(ar, len - 1);
    int tmp = ar[0]; ar[0] = ar[len - 1]; ar[len - 1] = tmp;
    heap_sort(ar, len - 1);
}

int main() {
    int ar[] = {4, 1, 6, 2, 9, 7, 3, 8};
    int len = sizeof ar / sizeof(int);
    
    heap_sort(ar, len);
    
    printf("[");
    for (int i = 0; i < len; i++) {
        printf("%d, ", ar[i]);
    }
    printf("\b\b]\n");
    
    return 0;
}

 
Go言語版。
heap_sort1.go

package main
import "fmt"

func build_heap(ar []int) []int {
    heapify := func (parent int) {
        last := len(ar) - 1
        v := ar[parent]
        for {
            child := parent * 2 + 1
            if child > last {break}
            if child != last && ar[child + 1] > ar[child] {child++}
            if v >= ar[child] {break}
            ar[parent] = ar[child]
            parent = child
        }
        ar[parent] = v
    }
    
    for i:= (len(ar) - 2) / 2; i >= 0; i-- {heapify(i)}
    return ar
}

func heap_sort(ar []int) []int {
    len := len(ar)
    if len <= 1 {return ar}
    ar = build_heap(ar)
    ar[0], ar[len - 1] = ar[len - 1], ar[0]
    return append(heap_sort(ar[:len - 1]), ar[len - 1])
}

func main() {
    ar := heap_sort([]int{4, 1, 6, 2, 9, 7, 3, 8})
    fmt.Println(ar)
}

 

※参考
http://www.th.cs.meiji.ac.jp/assets/researches/2005/omoto/heapsort.html