マージソート(?)を書いてみた

Twitterのタイムラインに、
「マージソート」ってキーワードが流れてきたので、想像で書いてみた。

想像っていうと嘘になっちゃうんだけど、
たしか、正月あたりに「大人のピタゴラスイッチ」って番組で、
しめじを使って説明してた気がしたので、
あとは、タイムラインで見つけたいくつかのヒントを元に実装してみた。

ヒントはこんな感じ。
・再帰する
・省メモリ(らしい)
・「マージする」という言葉から連想

で、こうなった。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use strict;
use warnings;
use integer;
use v5.10;
 
say join( ',', my_sort(4,7,2,5,3,1,9,8,6) );
 
sub my_sort {
    my @ary = @_;
    merge_sort( \@ary, 2 );
    return @ary;
}
 
sub merge_sort {
    my ( $ary_ref, $n ) = @_;
 
    print "---- n = $n ----\n";
    dump_array( $ary_ref, $n );
 
    my $cnt = scalar( @{$ary_ref} );
    for (my $i=0; $i<$cnt; $i+=$n) {
 
        my $a1 = $i;
        my $b1 = $i + ($n / 2);
        my $a2 = $a1 + ($n / 2);
        my $b2 = $b1 + ($n / 2);
 
        if ( $cnt <= $b1 ) {
            last; # マージすべきグループがない
        }
 
        # 2グループのケツの添え字をクリップ
        $b2 = $cnt if ( $cnt < $b2 );
 
        # マージ先の選択
        for (my $j=$b1; $j<$b2; $j++) {
            say 'merge: ', $ary_ref->[$j];
            for (my $k=$a1; $k<$b2; $k++) {
                if ( $ary_ref->[$k] < $ary_ref->[$j] ) {
                    my $wk = $ary_ref->[$j];
                    for (my $l=$j; $k<$l; $l--) {
                        swap( $ary_ref, $l, $l - 1 );
                    }
 
                    dump_array( $ary_ref, $n );
 
                    # マージ先グループの次の要素は、
                    # マージしようとしている値と同じか、より小さい
                    last;
                }
            }
        }
    }
 
    print "----  end  ----\n\n";
 
    if ( $n < scalar(@{$ary_ref}) ) {
        merge_sort( $ary_ref, $n * 2 );
    }
}
 
sub swap {
    my ( $ary_ref, $a, $b ) = @_;
    my $wk = $ary_ref->[$b];
    $ary_ref->[$b] = $ary_ref->[$a];
    $ary_ref->[$a] = $wk;
}
 
sub dump_array {
    my ( $ary_ref, $n ) = @_;
  
    my $cnt = scalar( @{$ary_ref} );
    my $i = 0;
    while ( $cnt ) {
        print '[';
        for (my $j=0; $j<$n; $j++) {
            if ( 0 < $cnt ) {
                print $ary_ref->[$i++];
                $cnt--;
                if ( 0 < $cnt and $j < ($n - 1) ) {
                    print ',';
                }
            }  
        }
        print ']';
    }
 
    print "\n";
}

結果は、こんな感じ。
$ perl aaa.pl
---- n = 2 ----
[4,7][2,5][3,1][9,8][6]
merge: 7
[7,4][2,5][3,1][9,8][6]
merge: 5
[7,4][5,2][3,1][9,8][6]
merge: 1
merge: 8
---- end ----

---- n = 4 ----
[7,4,5,2][3,1,9,8][6]
merge: 5
[7,5,4,2][3,1,9,8][6]
merge: 2
merge: 9
[7,5,4,2][9,3,1,8][6]
merge: 8
[7,5,4,2][9,8,3,1][6]
---- end ----

---- n = 8 ----
[7,5,4,2,9,8,3,1][6]
merge: 9
[9,7,5,4,2,8,3,1][6]
merge: 8
[9,8,7,5,4,2,3,1][6]
merge: 3
[9,8,7,5,4,3,2,1][6]
merge: 1
---- end ----

---- n = 16 ----
[9,8,7,5,4,3,2,1,6]
merge: 6
[9,8,7,6,5,4,3,2,1]
---- end ----

9,8,7,6,5,4,3,2,1

日付が変わる前を目標に勢いで書いたので自信ないけど、
一応、ソートできたので載せておきます。

おしまい。

2013/09/06 追記
マージ先の探索範囲を狭くしていかないとアレですね。
という訳で、そのうち、ちゃんと調べてから書いてみようと思います。

Leave a Comment