Заметки по книге - Алгоритмы. Построение и анализ


Сортировка вставкой

Вход:

  • Массив чисел (a1, a2, a3, …, an);

Выход:

  • Сортированный массив в возростающем порядке

Insertion-Sort(A)

1. Для i = 1 до конца массива
    1.1. Установить key = A[i]
    1.2. Установить j = i-1
    1.3. Пока j > 0 И A[j] > key
        1.3.1. Установить A[j+1] = A[j]
        1.3.2. Установить j = j - 1
    1.4. Установить A[j+1] = key
Пример

my @origin_array = (12, 9, 7, 7, 10, 5, 7);
my @sort_array = MySort::insertion_sort(@origin_array);

warn "\n\n";
warn "result = @sort_array";

sub insertion_sort {
    my @array = @_;

    for ( my $i = 1; $i < @array; $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;
}

$ perl script.pl 
result = 5 7 7 7 9 10 12 at script.pl line 17.
    
benchmark

my ( $check, $size) = ( 1, 9999 );
my @origin_array = (0..$size);
my $length = scalar(@origin_array);

timethese(0, {
    'insertion' =>  sub {
        my @shuffle_array = shuffle @origin_array;
        my @sort_array = MySort::insertion_sort(@shuffle_array);
        check_sort(\@sort_array, \@origin_array) if $check;
    },
});

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 insertion for at least 3 CPU seconds...
 insertion:  6 wallclock secs ( 5.89 usr +  0.00 sys =  5.89 CPU) @  0.34/s (n=2)
            (warning: too few iterations for a reliable count)
    

Сортировка слиянием

Вход:

  • Массив целых чисел (a1, a2, a3, …, an);

Выход:

  • Сортированный массив в возростающем порядке

Merge-Sort(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.
    
Сравнение двух алгоритмов

При достаточно малом кол-ве элементов(здесь перемешанный массив из 100 эл.), оба алгоритма примерно равны. Метод разделения даже немного проигрывает


$ perl benchmark.pl 
Benchmark: running insertion, merge_sort for at least 3 CPU seconds...
 insertion:  3 wallclock secs ( 3.05 usr +  0.00 sys =  3.05 CPU) @ 2899.67/s (n=8844)
merge_sort:  3 wallclock secs ( 3.23 usr +  0.00 sys =  3.23 CPU) @ 2535.60/s (n=8190)
    

Но когда элементов становиться больше(в данном случае 10к), разница ощущается


$ perl benchmark.pl 
Benchmark: running insertion, merge_sort for at least 3 CPU seconds...
 insertion:  3 wallclock secs ( 3.49 usr +  0.00 sys =  3.49 CPU) @  0.29/s (n=1)
            (warning: too few iterations for a reliable count)
merge_sort:  2 wallclock secs ( 3.12 usr +  0.00 sys =  3.12 CPU) @ 12.50/s (n=39)
    

Упражнения 1

2.1.1

Используя рис. 2.2 в качестве образца, проиллюстрируйте работу процедуры Insertion-Sort по сортировке массива A = (31, 41, 59, 26, 41, 58)

a. (31, 41, 59, 26, 41, 58);
        <-  

b. (31, 41, 59, 26, 41, 58);
            <-
c. (31, 41, 59, 26, 41, 58);
    ->  ->  ->   |
    |<-----------|

d. (26, 31, 41, 59, 41, 58);
                ->   |
                <----|

e. (26, 31, 41, 41, 59, 58);

e. (26, 31, 41, 41, 59, 58);
                    ->   |
                    <----|
2.1.2

Перепишите процедуру Insertion-Sort для сортировки в невозрастающем порядке вместо неубывающего

Изменил знак с большего на меньшее, проверил - работает


sub insertion_sort {
    my @array = @_;

    for ( my $i = 1; $i < @array; $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;
}
2.1.3
Рассмотрим задачу поиска.
Вход: Массив чисел и значение u
Выход: индекс i такой что u = A[i], или спец. знач. undef если u в A отсутствует

Составьте псевдокод линейного поиска, при работе которого выполняется сканирование массива в поисках значения u. Докажите корректность алгоритма с помощью инварианта цикла. Убедитесь, что выбранный инвариант цикла удовлетворяет трем необходимым условиям.
``` 1. Для i до конца массива A. Если A[i] == u, то вернуть i 2. Вернуть undef ```

инвариант не делал

2.1.4 - todo

Рассмотрим задачу сложения двух n-битовых двоичных целых чисел, хранящихся в n-элементных массивах A и B. Сумму этих двух чисел необходимо занести в двоичной форме в (n+1)-элементный массив C. Приведите строгую формулировку задачи и составьте псевдокод для сложения этих двух чисел.

Упражнения 2

2.2.1 - todo

Выразите функцию n^3/1000 — 100n^2 — 100n + 3 в ©-обозначениях.

2.2.2 - todo
Рассмотрим сортировку элементов массива А, которая выполняется следующим
образом. Сначала определяется наименьший элемент массива А, который ста­вится на место элемента А[1]. Затем производится поиск второго наименьшего элемента массива А, который ставится на место элемента А[2]. Этот процесс про­должается для первых n — 1 элементов массива А. Запишите псевдокод этого алгоритма, известного как сортировка выбором (selection sort). Какой инвариант
цикла сохраняется для этого алгоритма? Почему его достаточно выполнить для первых п — 1 элементов, а не для всех п элементов? Определите время работы алгоритма в наилучшем и наихудшем случаях и запишите его в ©-обозначениях.
2.2.3 - todo
Вновь обратимся к алгоритму линейного поиска (см. упр. 2.1.3). Для скольких элементов входной последовательности в среднем нужно произвести проверку, если предполагается, что все элементы массива с равной вероятностью могут иметь искомое значение? Что происходит в наихудшем случае? Чему равно вре­мя работы алгоритма линейного поиска в среднем и в наихудшем случаях в ©-
обозначениях? Обоснуйте свой ответ.
2.2.4 - todo

Каким образом можно модифицировать почти каждый алгоритм, чтобы получить оптимальное время работы в наилучшем случае?

Упражнения 3