シェルソートを書いてみた(中編)
シェルソートがあまりにも速いので、
まずは、何がどうなって、どうして速いのか検証してみる。
use strict;
use warnings;
use v5.10;
my @data = 1..9;
say join( ', ', @data );
say '-=' x 22;
my @sorted = shell_sort( @data );
say '-=' x 22;
say join( ', ', @sorted );
sub shell_sort {
my @data = @_;
my $cnt = scalar( @data );
my $gap = $cnt;
while ( ($gap = int($gap / 2)) ) {
printf( "--- \$gap =%2d ---\n", $gap );
say join( ', ', @data );
for (my $i=$gap; $i<$cnt; $i++) {
printf( "\$i =%2d\n", $i );
dump_array( \@data, $i, $gap );
my $wk = $data[$i];
my $j = $i;
for (; $gap<=$j; $j-=$gap) {
if ( $wk <= $data[$j-$gap] ) {
last;
}
else {
$data[$j] = $data[$j-$gap];
dump_array( \@data, $i, $gap, ($j - $gap) );
}
}
$data[$j] = $wk;
dump_array( \@data, $i, $gap );
}
say join( ', ', @data );
}
return @data;
}
sub dump_array {
my ( $ary_ref, $base, $gap ) = @_;
my $invalid_index = $_[3] // -1;
my @wk;
my $cnt = scalar( @{$ary_ref} );
for (my $i=0; $i<$cnt; $i++) {
my $val = ( $invalid_index == $i ) ? 'x' : $ary_ref->[$i];
if ( (($i - $base) % $gap) == 0 ) {
push @wk, qq/[$val]/;
}
else {
push @wk, qq/ $val /;
}
}
say join( ', ', @wk );
}
実行結果はこんな感じ。
$ perl aaa.pl
1, 2, 3, 4, 5, 6, 7, 8, 9
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
--- $gap = 4 ---
1, 2, 3, 4, 5, 6, 7, 8, 9
$i = 4
[1], 2 , 3 , 4 , [5], 6 , 7 , 8 , [9]
[x], 2 , 3 , 4 , [1], 6 , 7 , 8 , [9]
[5], 2 , 3 , 4 , [1], 6 , 7 , 8 , [9]
$i = 5
5 , [2], 3 , 4 , 1 , [6], 7 , 8 , 9
5 , [x], 3 , 4 , 1 , [2], 7 , 8 , 9
5 , [6], 3 , 4 , 1 , [2], 7 , 8 , 9
$i = 6
5 , 6 , [3], 4 , 1 , 2 , [7], 8 , 9
5 , 6 , [x], 4 , 1 , 2 , [3], 8 , 9
5 , 6 , [7], 4 , 1 , 2 , [3], 8 , 9
$i = 7
5 , 6 , 7 , [4], 1 , 2 , 3 , [8], 9
5 , 6 , 7 , [x], 1 , 2 , 3 , [4], 9
5 , 6 , 7 , [8], 1 , 2 , 3 , [4], 9
$i = 8
[5], 6 , 7 , 8 , [1], 2 , 3 , 4 , [9]
[5], 6 , 7 , 8 , [x], 2 , 3 , 4 , [1]
[x], 6 , 7 , 8 , [5], 2 , 3 , 4 , [1]
[9], 6 , 7 , 8 , [5], 2 , 3 , 4 , [1]
9, 6, 7, 8, 5, 2, 3, 4, 1
--- $gap = 2 ---
9, 6, 7, 8, 5, 2, 3, 4, 1
$i = 2
[9], 6 , [7], 8 , [5], 2 , [3], 4 , [1]
[9], 6 , [7], 8 , [5], 2 , [3], 4 , [1]
$i = 3
9 , [6], 7 , [8], 5 , [2], 3 , [4], 1
9 , [x], 7 , [6], 5 , [2], 3 , [4], 1
9 , [8], 7 , [6], 5 , [2], 3 , [4], 1
$i = 4
[9], 8 , [7], 6 , [5], 2 , [3], 4 , [1]
[9], 8 , [7], 6 , [5], 2 , [3], 4 , [1]
$i = 5
9 , [8], 7 , [6], 5 , [2], 3 , [4], 1
9 , [8], 7 , [6], 5 , [2], 3 , [4], 1
$i = 6
[9], 8 , [7], 6 , [5], 2 , [3], 4 , [1]
[9], 8 , [7], 6 , [5], 2 , [3], 4 , [1]
$i = 7
9 , [8], 7 , [6], 5 , [2], 3 , [4], 1
9 , [8], 7 , [6], 5 , [x], 3 , [2], 1
9 , [8], 7 , [6], 5 , [4], 3 , [2], 1
$i = 8
[9], 8 , [7], 6 , [5], 4 , [3], 2 , [1]
[9], 8 , [7], 6 , [5], 4 , [3], 2 , [1]
9, 8, 7, 6, 5, 4, 3, 2, 1
--- $gap = 1 ---
9, 8, 7, 6, 5, 4, 3, 2, 1
$i = 1
[9], [8], [7], [6], [5], [4], [3], [2], [1]
[9], [8], [7], [6], [5], [4], [3], [2], [1]
$i = 2
[9], [8], [7], [6], [5], [4], [3], [2], [1]
[9], [8], [7], [6], [5], [4], [3], [2], [1]
$i = 3
[9], [8], [7], [6], [5], [4], [3], [2], [1]
[9], [8], [7], [6], [5], [4], [3], [2], [1]
$i = 4
[9], [8], [7], [6], [5], [4], [3], [2], [1]
[9], [8], [7], [6], [5], [4], [3], [2], [1]
$i = 5
[9], [8], [7], [6], [5], [4], [3], [2], [1]
[9], [8], [7], [6], [5], [4], [3], [2], [1]
$i = 6
[9], [8], [7], [6], [5], [4], [3], [2], [1]
[9], [8], [7], [6], [5], [4], [3], [2], [1]
$i = 7
[9], [8], [7], [6], [5], [4], [3], [2], [1]
[9], [8], [7], [6], [5], [4], [3], [2], [1]
$i = 8
[9], [8], [7], [6], [5], [4], [3], [2], [1]
[9], [8], [7], [6], [5], [4], [3], [2], [1]
9, 8, 7, 6, 5, 4, 3, 2, 1
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
9, 8, 7, 6, 5, 4, 3, 2, 1
このデータ列は、挿入ソートでは最悪の結果をもたらす。
しかし、$gapが1になる直前を見て欲しい。
すでに、ソートが終わっている!?
それどころか、$gapが2になる前に、
5より大きいものと、5より小さいものが入れ替わっている!!
自分が理解できる範囲でまとめると、
通常の挿入ソートを行う前に、挿入ソートで時間の掛かるケース、
つまり挿入先が遠くにある状態をあらかじめ回避している。
これなら、”ほとんど”ソートされた状態から挿入ソートが行われるので、
ソートが速くなるのも理解できる。
しかも、まだ改善の余地があるそうなので、
次回は、その辺を詰めてみようと思う。
続く。
Leave a Comment