Поиск элемента в массиве
Процедура Linear-Search(A, n, x)
Вход:
- A - массив
- n - кол-во элементов массива A, среди которых выполняется поиск
- x - искомое значение
Выход: либо индекс i, для которого A[i] = x, либо -1 - элемент не найден
1. Установить значение answer равным 'not found'
2. Для каждого i, от 0 до n
2.1. Если A[i] = x, установить answer равным i
3. Вернуть answer
Пример
use uni::perl qw| :dumper |;
use List::Util qw| shuffle |;
my $item = 189;
my @arr = shuffle (0..9999);
my $length = scalar @arr;
my $index = linear_search(\@arr, $length, $item);
print $index eq 'not found' ? "$index\n" : "Item = $item, index = $index, chech = $arr[$index]\n";
sub linear_search {
my ( $array, $length, $item ) = @_;
my $answer = 'not found';
for (my $i = 0; $i < $length; $i++) {
$answer = $i if $array->[$i] == $item;
}
return $answer;
}
$ perl script.pl
Item = 189, index = 1525, chech = 189
Процедура Better-Linear-Search(A, n, x)
Вход и Выход: теже что и в Linear-Search
1. Для i = 0 до n
1.1. Если A[i] = x, вернуть i
2. Вернуть 'not found'
Пример
my $item = 1;
my @arr = shuffle (0..9999);
my $length = scalar @arr;
my $index = better_linear_search(\@arr, $length, $item);
print $index eq 'not found' ? "$index\n" : "Item = $item, index = $index, chech = $arr[$index]\n";
sub better_linear_search {
my ( $array, $length, $item ) = @_;
for (my $i = 0; $i < $length; $i++){
return $i if $array->[$i] == $item;
}
return 'not found';
}
$ perl script.pl
Item = 1, index = 9227, chech = 1
Процедура Sentinel-Linear-Search(A, n, x)
Вход и выход: теже что и у Linear-Search
1. Если A[n-1] = x, то вернуть n-1
2. Поместить x в A[n-1]
3. Установить i = 0
4. Пока A[i] != x:
4.1. Увеличить i на 1
5. Если i < n-1, вернуть i
6. В противном случае, вернуть 'not found'
Пример
my $item = 2;
my @arr = shuffle (0..9999);
my $length = scalar @arr;
my $index = sentinel_linear_search(\@arr, $length, $item);
print $index eq 'not found' ? "$index\n" : "Item = $item, index = $index, chech = $arr[$index]\n";
sub sentinel_linear_search {
my ( $array, $length, $item ) = @_;
return $length - 1 if $array->[$length - 1] == $item;
$array->[$length - 1] = $item;
my $index = 0;
while ( $array->[$index] != $item ){
$index++;
}
return $index if $index < $length - 1;
return 'not found';
}
$ perl script.pl
Item = 2, index = 9328, chech = 2
Benchmark
Benchmark всех трех алгоритмов
use Benchmark;
my ( $shuffle, $check, $size ) = (1, 0, 999999);
my @array = $shuffle ? shuffle (0..$size) : (0..$size);
my $length = scalar(@array);
timethese(0, {
'linear' => sub {
my $item = int rand @array;
my $index = MySearch::linear_search( \@array, $length, $item );
ok( $item == $array[$index] ) if $check;
},
'better' => sub {
my $item = int rand @array;
my $index = MySearch::better_linear_search( \@array, $length, $item );
ok( $item == $array[$index] ) if $check;
},
'sentinel' => sub {
my $item = int rand @array;
my $index = MySearch::sentinel_linear_search( \@array, $length, $item );
ok( $item == $array[$index] ) if $check;
},
});
done_testing() if $check;
$ perl benchmark.pl
Benchmark: running better, linear, sentinel for at least 3 CPU seconds...
better: 4 wallclock secs ( 3.68 usr + 0.00 sys = 3.68 CPU) @ 22.28/s (n=82)
linear: 3 wallclock secs ( 3.11 usr + 0.01 sys = 3.12 CPU) @ 12.18/s (n=38)
sentinel: 3 wallclock secs ( 3.36 usr + 0.01 sys = 3.37 CPU) @ 21.36/s (n=72)
$ perl benchmark.pl
Benchmark: timing 100 iterations of better, linear, sentinel...
better: 5 wallclock secs ( 4.54 usr + 0.01 sys = 4.55 CPU) @ 21.98/s (n=100)
linear: 8 wallclock secs ( 8.29 usr + 0.00 sys = 8.29 CPU) @ 12.06/s (n=100)
sentinel: 4 wallclock secs ( 4.08 usr + 0.06 sys = 4.14 CPU) @ 24.15/s (n=100)
Результат - better_search работатет в 2 раза быстрее linear_search и на 0.8 мил. быстрее sentinel_search
Рекурсия
Канонический пример рекурсии. Вычисление n!
Процедура Factorial(n)
Вход: целое число n >= 0 Выход: значение n!
1. Если n = 0, вернуть 1
2. В противном случае вернуть n * Factorial(n - 1)
Пример
print factorial(5) . "\n\n";
sub factorial {
my $n = shift;
return ( $n == 0 ? 1 : $n * factorial( $n - 1 ) ) ;
}
$ perl script.pl
120
Процедура Recursive-Linear-Search(A, n, i, x)
Вход: теже что и у Linear-Search, но с доп. параметром i - откуда начинать искать Выход: тотже
1. Если i > n, вернуть 'not found'
2. В противном случае (i <= n), если A[i] = x, вернуть i
3. В противном случае (i <= n и A[i] != x), вернуть Recursive-Linear-Search(A, n, i + 1, x)
Пример
sub recursive_linear_search {
my ( $array, $length, $index, $item ) = @_;
return 'not found' if $index > $length;
return $index if $array->[$index] == $item;
return recursive_linear_search($array, $length, ++$index, $item);
}
$ perl script.pl
Item = 1, index = 618, chech = 1
Самый медленный поиск - рекурсивный
my ( $shuffle, $check, $size ) = (1, 0, 999999);
my @array = $shuffle ? shuffle (0..$size) : (0..$size);
my $length = scalar(@array);
timethese(0, {
'linear' => sub {
my $item = int rand @array;
my $index = MySearch::linear_search( \@array, $length, $item );
ok( $item == $array[$index] ) if $check;
},
'better' => sub {
my $item = int rand @array;
my $index = MySearch::better_linear_search( \@array, $length, $item );
ok( $item == $array[$index] ) if $check;
},
'sentinel' => sub {
my $item = int rand @array;
my $index = MySearch::sentinel_linear_search( \@array, $length, $item );
ok( $item == $array[$index] ) if $check;
},
'reqursive' => sub {
my $item = int rand @array;
my $index = MySearch::recursive_linear_search( \@array, $length, 0, $item );
ok( $item == $array[$index] ) if $check;
},
});
$ perl benchmark.pl
Benchmark: running better, linear, reqursive, sentinel for at least 3 CPU seconds...
better: 4 wallclock secs ( 3.21 usr + 0.00 sys = 3.21 CPU) @ 25.23/s (n=81)
linear: 3 wallclock secs ( 3.11 usr + 0.00 sys = 3.11 CPU) @ 11.90/s (n=37)
reqursive: 3 wallclock secs ( 3.30 usr + 0.00 sys = 3.30 CPU) @ 3.64/s (n=12)
sentinel: 3 wallclock secs ( 3.14 usr + 0.00 sys = 3.14 CPU) @ 23.57/s (n=74)
$ perl benchmark.pl
Benchmark: timing 100 iterations of better, linear, reqursive, sentinel...
better: 5 wallclock secs ( 4.13 usr + 0.02 sys = 4.15 CPU) @ 24.10/s (n=100)
linear: 8 wallclock secs ( 8.18 usr + 0.01 sys = 8.19 CPU) @ 12.21/s (n=100)
reqursive: 22 wallclock secs (20.86 usr + 0.20 sys = 21.06 CPU) @ 4.75/s (n=100)
sentinel: 5 wallclock secs ( 4.52 usr + 0.01 sys = 4.53 CPU) @ 22.08/s (n=100)
Сортировка
Бинарный поиск
Ключевая особенность - массив должен быть отсортирован. Время работы алгоритма O(lgn)
TODO: тут еще можно много чего придумать. Например в статье - https://habr.com/post/146228/
ищется в зависимости от того в какую сторону сортирован массив
Процедура Binary-Search(A, n, x)
Вход и выход: теже что и у Linear-Search
То что написано в книге, у меня не взлетело… Вечно вылетало в бесконечный цикл поэтому я решил немного подправить
1. Установить left = 0, а right = n
2. Пока left <= right:
2.1. Установить middle = (left + right)/2
2.2. Если A[middle] = x, вернуть middle
2.3. В противном случае (A[q] != x), если A[q] > x, установить right = middle
2.4. В противном случае (A[q] < x), установить left = middle - 1
3. Вернуть значение 'not found'
Пример
my ( $shuffle, $check, $size ) = (0, 1, 999999);
my @array = $shuffle ? shuffle (0..$size) : (0..$size);
my $length = scalar(@array);
my $item = int rand @array;
my $index = MySearch::binary_search( \@array, $length, $item );
warn "item = $item; index = $index; check = $array[$index]" if $check;
ok( $item == $array[$index] ) if $check;
done_testing() if $check;
sub binary_search {
my ( $array, $length, $item ) = @_;
my ( $left, $right ) = ( 0, $length );
while ( $left <= $right ){
my $middle = int ( ($right + $left) / 2 );
return $middle if $array->[$middle] == $item;
$array->[$middle] > $item ? $right = $middle : $left = $middle++;
}
return 'not found';
}
$ perl script.pl
item = 431667; index = 431667; check = 431667 at script.pl line 18.
ok 1
benchmark, пока что самые лучшие результаты
my ( $shuffle, $check, $size ) = (0, 0, 999999);
my @array = $shuffle ? shuffle (0..$size) : (0..$size);
my $length = scalar(@array);
timethese(0, {
'binary' => sub {
my $item = int rand @array;
my $index = MySearch::binary_search( \@array, $length, $item );
ok( $item == $array[$index] ) if $check;
},
});
done_testing() if $check;
$ perl benchmark.pl
Benchmark: running binary for at least 3 CPU seconds...
binary: 3 wallclock secs ( 3.21 usr + 0.01 sys = 3.22 CPU) @ 58165.22/s (n=187292)
$ perl benchmark.pl
Benchmark: timing 100 iterations of binary...
binary: 0 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CPU)
(warning: too few iterations for a reliable count)
Процедура Recursive-Binary-Search(A, p, r, x)
Вход и выход: теже что и у Linear-Search. Параметры p и r - подмассив A[p..r]
1. Если p > r, вернуть 'not found'
2. В противном случае (p <= r), установить q = ( p + r )/2
3. Если A[q] = x, вернуть q
4. В противном случае (A[q] != x), если A[q] > x, вернуть Recursive-Binary-Search(A, q, r, x)
5. В противном случае (A[q] <= x) вернуть Recursive-Binary-Search(A, p, q - 1, x)
Пример
my ( $shuffle, $check, $size ) = (0, 1, 99);
my @array = $shuffle ? shuffle (0..$size) : (0..$size);
my $length = scalar(@array);
my $item = int rand @array;
my $index = MySearch::recursive_binary_search( \@array, 0, $length, $item );
warn "item = $item; index = $index; check = $array[$index]" if $check;
ok( $item == $array[$index] ) if $check;
done_testing() if $check;
sub recursive_binary_search {
my ($array, $left, $right, $item) = @_;
return 'not found' if ( $left > $right );
my $middle = int ( ( $left + $right ) / 2 );
return $middle if ( $array->[$middle] == $item );
if ($array->[$middle] > $item){
recursive_binary_search($array, $left, $middle, $item);
} else {
recursive_binary_search($array, $middle, $right, $item);
}
}
$ perl script.pl
item = 85; index = 85; check = 85 at script.pl line 19.
ok 1
1..1
benchmark
По производительности немного уступает итеративному варианту
my ( $shuffle, $check, $size ) = (0, 0, 999999);
my @array = $shuffle ? shuffle (0..$size) : (0..$size);
my $length = scalar(@array);
timethese(0, {
'binary' => sub {
my $item = int rand @array;
my $index = MySearch::binary_search( \@array, $length, $item );
ok( $item == $array[$index] ) if $check;
},
'rec_binary' => sub {
my $item = int rand @array;
my $index = MySearch::recursive_binary_search( \@array, 0, $length, $item );
ok( $item == $array[$index] ) if $check;
},
});
done_testing() if $check;
$ perl benchmark.pl
Benchmark: running binary, rec_binary for at least 3 CPU seconds...
binary: 3 wallclock secs ( 3.10 usr + 0.00 sys = 3.10 CPU) @ 57728.71/s (n=178959)
rec_binary: 4 wallclock secs ( 3.06 usr + 0.00 sys = 3.06 CPU) @ 48827.12/s (n=149411)
Процедура Selection-Sort(A, n) - Сортировка выбором
Вход:
- A - сортируемый массив
- n - кол-во эл. массива
Выход: Сортированный массив в возростающем порядке
1. Для i = 0 до n-1
A. Установить значение small = i
B. Для j = i+1 до n-1
i. Если A[j] < A[small], присвоить small = j
C. Поменять меставми A[i] <=> A[small]
Пример
my $check = 1;
my @origin_array = (0..10);
my @shuffle_array = shuffle @origin_array;
my $length = scalar(@shuffle_array);
my @sort_array = MySort::selection_sort(\@shuffle_array, $length);
if ( $check ){
foreach my $i ( 0..$length - 1 ){
die "Array is different! origin_array = $origin_array[$i]; != $sort_array[$i]" if ( $origin_array[$i] != $sort_array[$i] );
}
}
sub selection_sort {
my ( $array, $size ) = @_;
for (my $i = 0; $i < $size; $i++) {
my $small = $i;
for (my $j = $i + 1; $j < $size; $j++) {
$small = $j if ( $array->[$j] < $array->[$small] );
}
( $array->[$i], $array->[$small] ) = ( $array->[$small], $array->[$i] );
}
return @{ $array || [] };
}
benchmark
По стравнению с обычным sort, selection_sort сильно отстает по производительности
100 итераций с 999999 массивом, я так и не дождался...
my ( $check, $size) = ( 0, 9999 );
my @origin_array = (0..$size);
my $length = scalar(@origin_array);
timethese(100, {
'selection_sort' => sub {
my @shuffle_array = shuffle @origin_array;
my @sort_array = MySort::selection_sort(\@shuffle_array, $length);
check_sort(\@sort_array, \@origin_array) if $check;
},
'origin_sort' => sub {
my @shuffle_array = shuffle @origin_array;
my @sort_array = sort { $a <=> $b } @shuffle_array;
check_sort(\@sort_array, \@origin_array);
}
});
sub check_sort {
my ( $origin_array, $sort_array ) = @_;
foreach my $i ( 0..$length - 1 ){
die "Array is different! origin_array = $origin_array->[$i]; sort_array = $sort_array->[$i]; "
if ( $origin_array->[$i] != $sort_array->[$i] );
}
}
$ perl benchmark.pl
Benchmark: running origin_sort, selection_sort for at least 3 CPU seconds...
origin_sort: 3 wallclock secs ( 3.18 usr + 0.00 sys = 3.18 CPU) @ 245.91/s (n=782)
selection_sort: 11 wallclock secs (10.78 usr + 0.00 sys = 10.78 CPU) @ 0.09/s (n=1)
(warning: too few iterations for a reliable count)
Процедура Insertion-Sort(A, n) - Сортировка вставкой
Вход и выход: такие же что и у Selection-Sort
1. Для i = 1 до n-1
A. Установить key = A[i], а j = i-1
B. Пока j > 0 и A[j] > key
i. Присвоить A[j+1] = A[j]
ii. Уменьшить j на еденицу
C. Присвоить A[j+1] = key
Пример
my $check = 1;
my @origin_array = (0..999);
my @shuffle_array = shuffle @origin_array;
my $length = scalar(@shuffle_array);
my @sort_array = MySort::insertion_sort(\@shuffle_array, $length);
if ( $check ){
foreach my $i ( 0..$length - 1 ){
die "Array is different! origin_array = $origin_array[$i]; != $sort_array[$i]" if ( $origin_array[$i] != $sort_array[$i] );
}
}
sub insertion_sort {
my ( $array, $length ) = @_;
for (my $i = 1; $i < $length; $i++) {
my $tmp = $array->[$i];
my $j = $i - 1;
while ( $j >= 0 && $array->[$j] > $tmp ){
$array->[$j + 1] = $array->[$j];
$j--;
}
$array->[$j + 1] = $tmp;
}
return @{ $array || [] };
}
benchmark
Сортировка 10 000 записей пока что лидирует обычный sort
За 3 сек. процессорного времени, обычный sort делает ~800 сортировок, а "вставка" и "выборка" только по одному...
my ( $check, $size) = ( 1, 9999 );
my @origin_array = (0..$size);
my $length = scalar(@origin_array);
timethese(1, {
'insertion' => sub {
my @shuffle_array = shuffle @origin_array;
my @sort_array = MySort::insertion_sort(\@shuffle_array, $length);
check_sort(\@sort_array, \@origin_array) if $check;
},
'selection' => sub {
my @shuffle_array = shuffle @origin_array;
my @sort_array = MySort::selection_sort(\@shuffle_array, $length);
check_sort(\@sort_array, \@origin_array) if $check;
},
'origin' => sub {
my @shuffle_array = shuffle @origin_array;
my @sort_array = sort { $a <=> $b } @shuffle_array;
check_sort(\@sort_array, \@origin_array);
}
});
$ perl benchmark.pl
Benchmark: timing 1 iterations of insertion, origin, selection...
insertion: 8 wallclock secs ( 7.46 usr + 0.00 sys = 7.46 CPU) @ 0.13/s (n=1)
(warning: too few iterations for a reliable count)
origin: 0 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CPU)
(warning: too few iterations for a reliable count)
selection: 11 wallclock secs (11.04 usr + 0.01 sys = 11.05 CPU) @ 0.09/s (n=1)
(warning: too few iterations for a reliable count)
$ perl benchmark.pl
Benchmark: running insertion, origin, selection for at least 3 CPU seconds...
insertion: 7 wallclock secs ( 7.51 usr + 0.01 sys = 7.52 CPU) @ 0.13/s (n=1)
(warning: too few iterations for a reliable count)
origin: 3 wallclock secs ( 3.16 usr + 0.00 sys = 3.16 CPU) @ 239.56/s (n=757)
selection: 11 wallclock secs (10.84 usr + 0.00 sys = 10.84 CPU) @ 0.09/s (n=1)
(warning: too few iterations for a reliable count)
Сортировка слиянием
То что в книге, вообще не получилось :(
content
### Процедура Merge-Search(A, p, r)
Вход:
* A - сортируемый массив
* p, r - начальный и конечный индексы подмассива A[p..r]
Результат: массив A отсортирован в возростающем порядке
```
1. Если p >= r, выполнить возврат из процедуры.
2. В противном случае:
A. Установить q = ( p + r )/2
B. Вызвать Merge-Sort(A, p, q)
C. Вызвать Merge-Sort(A, q + 1, r)
D. Вызвать Merge(A, p, q, r)
```
### Процедура Merge(A, p, q, r)
Вход:
* A - массив
* p, q, r - индексы в массиве A. Подмассивы A[p, q] и A[q + 1, r] считаются уже отсортированными
Результат: подмассив A[p,r] содержит эл. A[p, q] и A[q + 1, r], но в отсортированном порядке
```
```
Процедура Merge-Sort(A)
Вход:
- A - неотсортированный массив
Результат: массив A отсортирован в возростающем порядке
1. Если кол-во элементов в массиве меньше или всего один элемент, вернуть его
2. В противном случае:
A. Вычислить середину массива: кол-во эл. / 2
B. Вызвать Merge-Sort( A[0, middle - 1] ) и записать результат в массив left
С. Вызвать Merge-Sort( A[middle, длина массива - 1] ) и записать результат в массив right
D. Вызвать Merge(right, left)
Процедура Merge(right, left)
Вход:
- два отсортированных массива
Результат: один отсортированный массив
1. Установить значение i и j равным нулю
3. Обьявить пустой массив tmp
2. Пока i меньше кол-ва эл. массива left и j меньше кол-ва эл. right
A. Если left[$i] < right[j], добавить эл. left[$i] во временный массив tmp и увеличить i на 1
B. В противном случае(left[$i] > right[j]), добавить эл. right[$j] во временный массив tmp и увеличить j на 1
3. Если кол-во эл. в массиве left меньше i, то добавить их в массив tmp
4. Если кол-во эл. в массиве right меньше j, то добавить их в массив tmp
5. Вернуть получившийся массив
Пример
my @origin_array = (12, 9, 7, 7, 10, 5, 7);
my @sort_array = MySort::merge_sort(@origin_array);
warn "result = @sort_array";
sub merge_sort {
my @array = @_;
my $lenght = @array;
return @array if ( $lenght <= 1 );
my $mid = int ( $lenght / 2 );
my @left = merge_sort( @array[0..$mid - 1] );
my @right = merge_sort( @array[$mid..$lenght - 1] );
merge(\@left, \@right);
}
sub merge {
my ($left, $right) = @_;
my @a1 = @{ $left || [] };
my @a2 = @{ $right || [] };
my @tmp;
my ($i, $j) = (0, 0);
while ( $i < @a1 && $j < @a2 ){
push @tmp, ( $a1[$i] < $a2[$j] ? $a1[$i++] : $a2[$j++] );
}
push @tmp, @a1[$i..$#a1] if ( $i < @a1 );
push @tmp, @a2[$j..$#a2] if ( $j < @a2 );
@tmp;
}
$ perl script.pl
result = 5 7 7 7 9 10 12 at script.pl line 20.
Если с debug:
$ perl script.pl
origin array = 12 9 7 7 10 5 7
origin array = 12 9 7
origin array = 12
left = 12
origin array = 9 7
origin array = 9
left = 9
origin array = 7
right = 7
i = 0; j = 0
7 9
right = 7 9
i = 0; j = 0
i = 0; j = 1
7 9 12
left = 7 9 12
origin array = 7 10 5 7
origin array = 7 10
origin array = 7
left = 7
origin array = 10
right = 10
i = 0; j = 0
7 10
left = 7 10
origin array = 5 7
origin array = 5
left = 5
origin array = 7
right = 7
i = 0; j = 0
5 7
right = 5 7
i = 0; j = 0
i = 0; j = 1
5 7 7 10
right = 5 7 7 10
i = 0; j = 0
i = 0; j = 1
i = 0; j = 2
i = 0; j = 3
i = 1; j = 3
i = 2; j = 3
5 7 7 7 9 10 12
result = 5 7 7 7 9 10 12 at script.pl line 20.