シェルソートを書いてみた(後編)

かの有名なクヌース先生の力を借りて、
適切な間隔でシェルソートを行ってみました。

use strict;
use warnings;
use v5.10;
use Array::Shuffle qw/shuffle_array/;
use Time::HiRes qw/time/;

my @data = 1..20000;
shuffle_array( @data );

my @perl_sorted;
{
    my $st = time();
    @perl_sorted = sort { $b <=> $a } @data;
    say 'Perl  : ', time() - $st;
}

{
    my $st = time();
    my @sorted = shell_sort(
        \@data,
        sub { $_[0] * 2; },
        sub { int($_[0] / 2); }
    );
    say 'Simple: ', time() - $st;
}

{
    my $st = time();
    my @sorted = shell_sort(
        \@data,
        sub { ($_[0] * 2) + 1; },
        sub { int(($_[0] - 1) / 2); }
    );
    say 'Knuth1: ', time() - $st;
}

{
    my $st = time();
    my @sorted = shell_sort(
        \@data,
        sub { ($_[0] * 3) + 1; },
        sub { int(($_[0] - 1) / 3); }
    );
    say 'Knuth2: ', time() - $st;
}

sub shell_sort {
    my ( $data_ref, $inc, $dec ) = @_;
    my @data = @{$data_ref};

    my $gap = 1;
    my $cnt = scalar( @data );

    while ( $gap < $cnt ) {
        $gap = $inc->( $gap );
    }

    while ( ($gap = $dec->($gap)) ) {
        for (my $i=$gap; $i<$cnt; $i++) {
            my $wk = $data[$i];
            my $j = $i;
            for (; $gap<=$j; $j-=$gap) {
                if ( $wk <= $data[$j-$gap] ) {
                    last;
                }
                else {
                    $data[$j] = $data[$j-$gap];
                }
            }

            $data[$j] = $wk;
        }
    }
   
    return @data;
}

実行結果はこんな感じ。
$ perl aaa.pl
Perl : 0.00928997993469238
Simple: 1.65300297737122
Knuth1: 0.559776067733765
Knuth2: 0.531576871871948

という訳で、今回はランダムなデータ列で計測しましたが、
相変わらず、Perlの組み込み関数には勝てないものの、
最初の実装と比べると、だいぶ改善されています。
ただし、処理時間はデータ列に依存するので、
本来であれば、n回計測して平均を取る必要があります。

間隔を、1,2,4,8, ... でソートしてしまうと、
例えば、間隔が4以上の間は、
0,4,8 ... と、1,5,9, ... と、2,6,10, ... と、
3,7,11, ... のグループ単位でソートされてしまうので、
こういうのを防ぎながら、混ぜ合わせつつソートするために、
ソースコードのような間隔の計算を行っています。

たしかに、混ざり合わないと、例に挙げたグループのうち、
2グループには大きな数字が多く含まれていて、
残りの2グループには小さな数字が多く含まれていると、
間隔が1になっても、あまりソートされていないデータ列が出来てしまう気もしますが、
それを直感的に理解できるかと言われると、自分には難しいです。

という訳で、もうしばらくはシェルソートで引っ張る予定です。

おしまい。

Leave a Comment