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

1. Обзор

В этом руководстве мы собираемся обсудить алгоритм сортировки вставок и взглянуть на его реализацию на Java .

Insertion Sort - эффективный алгоритм заказа небольшого количества товаров. Этот метод основан на том, как игроки в карты сортируют руки по игральным картам.

Начнем с пустой левой руки и карт, положенных на стол.

Затем мы убираем одну карточку за раз со стола и вставляем ее в правильное положение в левой руке. Чтобы найти правильную позицию для новой карты, мы сравниваем ее с уже отсортированным набором карт в руке справа налево.

Давайте начнем с понимания шагов алгоритма в форме псевдокода.

2. Псевдокод

Мы собираемся представить наш псевдокод для сортировки вставкой как процедуру с именем INSERTION-SORT , принимая в качестве параметра массив A[1 .. n] из n элементов для сортировки. Алгоритм сортирует входной массив по месту (переставляя элементы в массиве A).

После завершения процедуры входной массив A содержит перестановку входной последовательности, но в отсортированном порядке:

INSERTION-SORT(A)

for i=2 to A.length
    key = A[i]    j = i - 1
    while j > 0 and A[j]> key
        A[j+1]= A[j]        j = j - 1
    A[j + 1]= key

Давайте кратко рассмотрим алгоритм выше.

Индекс i указывает позицию текущего элемента в массиве для обработки.

Мы начинаем со второго элемента, так как по определению массив с одним элементом считается отсортированным. Элемент с индексом i называется key . Имея key, вторая часть алгоритма занимается поиском правильного индекса. Если key меньше значения элемента в индексе j , то ключ перемещается на одну позицию влево. Процесс продолжается до тех пор, пока мы не достигнем элемента, который меньше ключа.

Важно отметить, что перед началом итерации для нахождения правильной позиции key в индексе i массив A[1 .. j - 1] уже sorted .

3. Императивная реализация

Для императивного случая мы собираемся написать функцию с именем insertionSortImperative , принимающую в качестве параметра массив целых чисел.

Функция начинает перебирать массив со второго элемента.

В любой момент времени во время итерации мы могли бы думать об этом массиве как о логически разделенном на две части; левая сторона - отсортированная, а правая сторона - элементы, которые еще не отсортированы.

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

public static void insertionSortImperative(int[]input) {
    for (int i = 1; i < input.length; i++) {
        int key = input[i];
        int j = i - 1;
        while (j >= 0 && input[j]> key) {
            input[j + 1]= input[j];
            j = j - 1;
        }
        input[j + 1]= key;
    }
}

Далее, давайте создадим тест для метода выше:

@Test
public void givenUnsortedArray__whenInsertionSortImperative__thenSortedAsc() {
    int[]input = {6, 2, 3, 4, 5, 1};
    InsertionSort.insertionSortImperative(input);
    int[]expected = {1, 2, 3, 4, 5, 6};
    assertArrayEquals("the two arrays are not equal", expected, input);
}

Вышеприведенный тест доказывает, что алгоритм правильно сортирует входной массив <6, 2, 3, 4, 5, 1> в ​​порядке возрастания.

4. Рекурсивная реализация

Функция для рекурсивного случая называется _insertionSortRecursive _ и принимает в качестве входных данных массив целых чисел (так же, как для императивного случая).

Отличие от императивного случая (несмотря на то, что он рекурсивный) состоит в том, что он ** вызывает перегруженную функцию со вторым аргументом, равным количеству элементов для сортировки.

Поскольку мы хотим отсортировать весь массив, мы передадим количество элементов, равное его длине:

public static void insertionSortRecursive(int[]input) {
    insertionSortRecursive(input, input.length);
}

Рекурсивный случай немного сложнее. Базовый случай возникает, когда мы пытаемся отсортировать массив с одним элементом. В этом случае мы ничего не делаем.

Все последующие рекурсивные вызовы сортируют предопределенную часть входного массива - начиная со второго элемента до достижения конца массива:

private static void insertionSortRecursive(int[]input, int i) {
    if (i <= 1) {
        return;
    }
    insertionSortRecursive(input, i - 1);
    int key = input[i - 1];
    int j = i - 2;
    while (j >= 0 && input[j]> key) {
        input[j + 1]= input[j];
        j = j - 1;
    }
    input[j + 1]= key;
}

Вот как выглядит стек вызовов для входного массива из 6 элементов:

insertionSortRecursive(input, 6)
insertionSortRecursive(input, 5) and insert the 6th item into the sorted array
insertionSortRecursive(input, 4) and insert the 5th item into the sorted array
insertionSortRecursive(input, 3) and insert the 4th item into the sorted array
insertionSortRecursive(input, 2) and insert the 3rd item into the sorted array
insertionSortRecursive(input, 1) and insert the 2nd item into the sorted array

Давайте также посмотрим тест для этого:

@Test
public void givenUnsortedArray__whenInsertionSortRecursively__thenSortedAsc() {
    int[]input = {6, 4, 5, 2, 3, 1};
    InsertionSort.insertionSortRecursive(input);
    int[]expected = {1, 2, 3, 4, 5, 6};
    assertArrayEquals("the two arrays are not equal", expected, input);
}

Вышеприведенный тест доказывает, что алгоритм правильно сортирует входной массив <6, 2, 3, 4, 5, 1> в ​​порядке возрастания.

5. Время и пространство Сложность

  • Время выполнения процедуры INSERTION-SORT составляет O (n ^ 2) ** .

Для каждого нового элемента мы выполняем итерацию справа налево по уже отсортированной части массива, чтобы найти правильную позицию. Затем мы вставляем его, сдвигая элементы на одну позицию вправо.

Алгоритм сортируется на месте, поэтому его пространственная сложность составляет O (1) для обязательной реализации и O (n) для рекурсивной реализации.

6. Заключение

В этом уроке мы увидели, как реализовать сортировку вставками.

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

Имейте в виду, что, несмотря на свою квадратичную сложность, он сортируется на месте без необходимости вспомогательного пространства, как в случае merge sort .

Весь код можно найти по адресу over на GitHub .