シェルソートを書いてみた(中編)
シェルソートがあまりにも速いので、
まずは、何がどうなって、どうして速いのか検証してみる。
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