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

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

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

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

で、こうなった。

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