Массив

Массивы – это простейшая структура данных, представляющая собой набор элементов одного типа, расположенных последовательно в памяти. Хранит набор значений (элементов массива), идентифицируемых по индексу или набору индексов, принимающих целые (или приводимые к целым) значения из некоторого заданного непрерывного диапазона.

Преимущества:

  • Быстрый доступ к элементам по индексу
  • Непрерывная область памяти

Недостатки:

  • Фиксированный размер
  • Неэффективное добавление/удаление элементов

Применение: Массивы подходят для хранения набора данных с фиксированным размером, где операции вставки и удаления элементов не требуются.

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

Сортировка пузырьком / Bubble sort

Или сортировка простыми обменами. Обходим массив от начала до конца, попутно меняя местами неотсортированные соседние элементы. В результате первого прохода на последнее место «всплывёт» максимальный элемент. Теперь снова обходим неотсортированную часть массива (от первого элемента до предпоследнего) и меняем по пути неотсортированных соседей. Второй по величине элемент окажется на предпоследнем месте. Если за проход не произошло ни одного обмена, то массив отсортирован. Продолжая в том же духе, будем обходить всё уменьшающуюся неотсортированную часть массива, запихивая найденные максимумы в конец. Очевидно, не более чем после n итераций массив будет отсортирован.

<?php
    
/**
 * Worst O(n^2)
 * Average O(n^2)
 * Best O(n)
 */
function bubbleSort(array $array): array {
    $length = count($array);
    for ($i = $length - 1; $i > 0; $i--) {
        $changes = false;
        for ($j = 0; $j < $i; $j++) {
            if ($array[$j] > $array[$j + 1]) {
                [$array[$j], $array[$j + 1]] = [$array[$j + 1], $array[$j]];
                $changes = true;
            }
        }
        if (!$changes) {
            return $array;
        }
    }

    return $array;
}

print_r(bubbleSort([5, 25, 10, 7, 6, 20, 21, 1, 2, 13]));

Сортировка вставками / Insertion sort

Элементы входной последовательности просматриваются по одному, и каждый новый поступивший элемент размещается в подходящее место среди ранее упорядоченных элементов.

<?php

/**
 * Worst O(n^2)
 * Average O(n^2)
 * Best O(n)
 */ 
function insertSort(array $arr): array {
    $count = count($arr);  
    for ($i = 1; $i < $count; $i++) {
        for ($j = $i; $j >= 1 && $arr[$j] < $arr[$j-1]; $j--) {
           [$arr[$j], $arr[$j-1]] = [$arr[$j-1], $arr[$j]];
        }
    }
  
    return $arr;
}

print_r(insertSort([3,4,1,2,5,9,6,7,8]));

Сортировка выбором / Selection sort

На очередной итерации будем находить минимум в массиве после текущего элемента и менять его с ним, если надо. Таким образом, после i-ой итерации первые i элементов будут стоять на своих местах. Нужно отметить, что эту сортировку можно реализовать двумя способами – сохраняя минимум и его индекс или просто переставляя текущий элемент с рассматриваемым, если они стоят в неправильном порядке.

<?php

/**
 * Worst O(n^2)
 * Average O(n^2)
 * Best O(n^2)
 */
function selectionSort(array $arr): array 
{
    $count = count($arr);
    
    for ($i = 0; $i < $count - 1; $i++) {
        $min = $i;
        
        for ($j = $i + 1; $j < $count; $j++) {
            if ($arr[$j] < $arr[$min]) {
                $min = $j;
            }
        }
        
        if ($min != $i) {
            [$arr[$i], $arr[$min]] = [$arr[$min], $arr[$i]];
        }
    }
    
    return $arr;
}

$array = [3,4,1,2,5,9,6,7,8];
print_r(selectionSort($array));

Быстрая сортировка / Quicksort

  • Выбрать из массива элемент, называемый опорным(pivot). Это может быть любой из элементов массива. От выбора опорного элемента не зависит корректность алгоритма, но в отдельных случаях может сильно зависеть его эффективность. В ранних реализациях, как правило, опорным выбирался первый элемент, что снижало производительность на отсортированных массивах. Для улучшения эффективности может выбираться средний, случайный элемент или (для больших массивов) медиана первого, среднего и последнего элементов. Медиана всей последовательности является оптимальным опорным элементом, но её вычисление слишком трудоёмко для использования в сортировке.
  • Сравнить все остальные элементы с опорным и переставить их в массиве так, чтобы разбить массив на три непрерывных отрезка, следующих друг за другом: «элементы меньшие опорного», «равные» и «большие». На практике массив обычно делят не на три, а на две части: например, «меньшие опорного» и «равные и большие»; такой подход в общем случае эффективнее, так как упрощает алгоритм разделения
  • Для отрезков «меньших» и «больших» значений выполнить рекурсивно ту же последовательность операций, если длина отрезка больше единицы.
<?php

/**
 * Worst O(n^2)
 * Average O(n logn)
 * Best  O(n logn)
 */ 
function quick(array $arr) : array {
    if (count($arr) < 2) {
        return $arr;
    } 
    
    $pivot = (int)(count($arr) / 2); // самый тупой выбор опорника 
    
    $less_arr = [];
    $more_arr = [];
    
    for ($i = 0; $i <count($arr); $i++) {
        if ($i == $pivot) {
            continue;
        }
        if ($arr[$i] < $arr[$pivot]) {
            $less_arr[] = $arr[$i];
        } else {
            $more_arr[] = $arr[$i];
        }
    }
     
    return array_merge(quick($less_arr), [$arr[$pivot]], quick($more_arr));
}


print_r(quick([3, 4, 1, 2, 5, 9, 6, 7, 8]));

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

Сортировка, основанная на парадигме «разделяй и властвуй». Разделим массив пополам, рекурсивно отсортируем части, после чего выполним процедуру слияния: поддерживаем два указателя, один на текущий элемент первой части, второй – на текущий элемент второй части. Из этих двух элементов выбираем минимальный, вставляем в ответ и сдвигаем указатель, соответствующий минимуму. Слияние работает за O(n), уровней всего logn, поэтому асимптотика O(n logn). Эффективно заранее создать временный массив и передать его в качестве аргумента функции. Эта сортировка рекурсивна, как и быстрая, а потому возможен переход на квадратичную при небольшом числе элементов.

<?php

 /**
 * Worst O(n log n)
 * Average O(n log n)
 * Best O(n log n)	
 */ 
function merge(array $arr) : array {
    
    if (count($arr) < 2) {
        return $arr;
    }
    
    if (count($arr) == 2) {
        if ($arr[0] <  $arr[1]) {
            return $arr;
        } else {
            return [$arr[1], $arr[0]];
        }
    } 
    
    $halved = array_chunk($arr, ceil(count($arr)/2)); // пополам
   
    // разбиваем и сортируем каждую половину
    $h1 = merge($halved[0]);
    $h2 = merge($halved[1]);

    //сливаем
    $res = [];
    for ($i = 0, $j = 0; $i < count($h1) || $j < count($h2); ) {
        
        if (!isset($h1[$i])) {
            $res[] = $h2[$j];
            $j++;
            continue;
        }
        
        if (!isset($h2[$j])) {
            $res[] = $h1[$i];
            $i++;
            continue;
        }
        
       if ($h1[$i] < $h2[$j]) {
           $res[] = $h1[$i];
           $i++;
       } else {
           $res[] = $h2[$j];
           $j++; 
       }
    }

    return $res;
}

var_dump(merge([3,34,1,33,5,213213,99,1,1,0,0, -123]));

Сортировка кучей

Куча (heap) — это не что иное, как двоичное дерево с некоторыми дополнительными правилами, которым оно должно следовать: во-первых, оно всегда должно иметь структуру кучи, где все уровни двоичного дерева заполняются слева направо, и, во-вторых, оно должно быть упорядочено в виде max-кучи или min-кучи. В качестве примера я буду использовать min-кучу.

Алгоритм пирамидальной сортировки — это метод сортировки, который полагается на такие структуры данных как двоичные кучи. Поскольку мы знаем, что кучи всегда должны соответствовать определенным требованиям, мы можем использовать это для поиска элемента с наименьшим значением, последовательно сортируя элементы, выбирая корневой узел кучи и добавляя его в конец массива.

Работает в худшем, в среднем и в лучшем случае (то есть гарантированно) за O(n*log n) операций при сортировке n элементов. Количество применяемой служебной памяти не зависит от размера массива O(1). Может рассматриваться как усовершенствованная сортировка пузырьком, в которой элемент всплывает/тонет по многим путям.

<?php

// Реализация пирамидальной сортировки на Php

// Процедура для преобразования в двоичную кучу поддерева с корневым узлом i, что является
// индексом в arr[]. n - размер кучи

function heapify(&$arr, $n, $i)
{
    $largest = $i; // Инициализируем наибольший элемент как корень
    $l = 2*$i + 1; // левый = 2*i + 1
    $r = 2*$i + 2; // правый = 2*i + 2

    // Если левый дочерний элемент больше корня
    if ($l < $n && $arr[$l] > $arr[$largest])
        $largest = $l;

    //Если правый дочерний элемент больше, чем самый большой элемент на данный момент
    if ($r < $n && $arr[$r] > $arr[$largest])
        $largest = $r;

    // Если самый большой элемент не корень
    if ($largest != $i)
    {
        $swap = $arr[$i];
        $arr[$i] = $arr[$largest];
        $arr[$largest] = $swap;

        // Рекурсивно преобразуем в двоичную кучу затронутое поддерево
        heapify($arr, $n, $largest);
    }
}

//Основная функция, выполняющая пирамидальную сортировку
function heapSort(&$arr, $n)
{
    // Построение кучи (перегруппируем массив)
    for ($i = $n / 2 - 1; $i >= 0; $i--)
        heapify($arr, $n, $i);

    //Один за другим извлекаем элементы из кучи
    for ($i = $n-1; $i >= 0; $i--)
    {
        // Перемещаем текущий корень в конец
        $temp = $arr[0];
            $arr[0] = $arr[$i];
            $arr[$i] = $temp;

        // вызываем процедуру heapify на уменьшенной куче
        heapify($arr, $i, 0);
    }
}

/* Вспомогательная функция для вывода на экран массива размера n */
function printArray(&$arr, $n)
{
    for ($i = 0; $i < $n; ++$i)
        echo ($arr[$i]." ") ; 
} 

$arr = array(12, 11, 13, 5, 6, 7);
$n = sizeof($arr)/sizeof($arr[0]);
heapSort($arr, $n);
printArray($arr , $n);

Дополнительно: