シェルソートを書いてみた(後編)
かの有名なクヌース先生の力を借りて、
適切な間隔でシェルソートを行ってみました。
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