Введение

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

Распространенной аналогией для описания того, как работает этот алгоритм, является то, как мы, люди, сортируем карты в руках. Как мы это делаем, мы сохраняем отсортированные карты слева, а несортированные карты справа (по крайней мере, для правшей), и мы перебираем карты справа. Для каждой карты мы сравниваем ее с картами слева и вставляем в правильное положение.

Алгоритм

Алгоритм такой (сортировка по возрастанию):

  1. Начните со второго элемента (arr[1]) и сравните его с первым элементом (arr[0]).
  2. Если arr[1] меньше, чем arr[0], поменяйте их местами.
  3. Перейдите к третьему элементу (arr[2]) и сравните его с arr[1]. Если arr[2] меньше, замените его на arr[1]. Затем сравните arr[1] с arr[0] и при необходимости поменяйте местами.
  4. Повторите этот процесс для всех последующих элементов, пока весь массив не будет отсортирован.


Иллюстрация

Step 1
arr = [5, 2, 4, 6, 1]
arr[0] = 5
arr[1] = 2

Step 2 
arr[0] > arr[1] --> True. Found unsorted values. Swapping them
arr = [2, 5, 4, 6, 1]

Step 3
arr = [2, 5, 4, 6, 1]
arr[1] = 5
arr[2] = 4
arr[1] > arr[2] --> True. Found unsorted values. Swapping them

Comparing the previous pair:
arr = [2, 4, 5, 6, 1]
arr[0] = 2
arr[1] = 4
arr[0] > arr[1] --> False.

Step 4
arr = [2, 4, 5, 6, 1]
arr[2] = 5
arr[3] = 6
arr[2] > arr[3] --> False. 

arr = [2, 4, 5, 6, 1]
arr[3] = 6
arr[4] = 1
arr[3] > arr[4] --> True. Found unsorted values. Swapping them. 

Comparing the previous pair:
arr = [2, 4, 5, 1, 6]
arr[2] = 5
arr[3] = 1
arr[2] > arr[3] --> True. Found unsorted values. Swapping them. 

Comparing the previous pair:
arr = [2, 4, 1, 5, 6]
arr[1] = 4
arr[2] = 1
arr[1] > arr[2] --> True. Found unsorted values. Swapping them. 

Comparing the previous pair:
arr = [2, 1, 4, 5, 6]
arr[0] = 2
arr[1] = 1
arr[0] > arr[1] --> True. Found unsorted values. Swapping them.

Done.
arr = [1, 2, 4, 5, 6]

Как мы видим, на каждой итерации мы «перемещаем» наименьшее значение в правильное место в массиве.

Псевдокод

for j=1 to arr.Length:
    key = arr[j] # Set current index
    i = j - 1 # Set previous index

    # Found unsorted values
    while arr[i] > key and i >= 0:
        arr[i+1] = arr[i] # Swapping them
        i = i - 1 # Comparing the previous pair

    arr[i+1] = key # Inserting the key in the correct position

Код

питон

def insertion_sort(arr):

    for j in range(1, len(arr)):
        key = arr[j]
        i = j - 1

        while arr[i] > key and i >= 0:
            arr[i+1] = arr[i]
            i -= 1

        arr[i+1] = key

    return arr

arr = [5, 2, 4, 6, 1]
sorted_array = insertion_sort(arr)
print(sorted_array)

# [1, 2, 4, 5, 6]

C

#include <stdio.h>
#include <stdlib.h>

#define MAXSIZE 5 /*Array length*/

void insertion_sort(int arr[]);

int main(void)
{
    /*Initialize variables*/
    int i;
    int arr[MAXSIZE] = {5, 2, 4, 6, 1};

    /*Apply insertion sort*/
    insertion_sort(arr);

    /*Print results*/
    for (i = 0; i < MAXSIZE; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");

    /*return stats*/
    return 0;
}

void insertion_sort(int arr[])
{
    int i, j, key;
    key = 0;

    for (j = 1; j < MAXSIZE; j++)
    {
        key = arr[j]; /*Set current index*/
        i = j - 1; /*Set previous index*/

        /*Found unsorted values*/
        while(arr[i] > key && i >= 0)
        {
            arr[i+1] = arr[i]; /*Swapping them*/
            i = i - 1; /*Comparing the previous pair*/
        }
        arr[i+1] = key; /*Inserting the key in the correct position*/
    }
}
gcc insertion_sort.c -o insertion_sort
./insertion_sort

# [1, 2, 4, 5, 6]

Обсуждение

Время выполнения — O(n²)

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

Следовательно, общее количество итераций равно n * (n-j+1), что равно O(n²).

В лучшем случае массив уже отсортирован, и нам нужно выполнить итерацию по массиву только один раз, поэтому O(n).

Сложность пространства — O(1)

Поскольку мы сортируем массив на месте и отслеживаем только текущий и предыдущий индексы, независимо от размера входных данных, объемная сложность составляет O(1).

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

  • Легко реализовать.
  • Стабильный
  • Эффективен при небольшом количестве элементов.
  • Эффективен, когда элементы почти отсортированы.
  • Сортируйте на месте, таким образом резервируя ресурсы.

Доказательство правильности

Доказать правильность алгоритма можно по индукции.

Предположим, что алгоритм верен для первых j-1 элементов массива (т.е. мы предполагаем, что arr[0..j-1] отсортирован). Докажем, что алгоритм корректен для первых j элементов массива.

В начале j-й итерации алгоритм будет сравнивать j-й элемент с j-1-м элементом. Мы можем разделить это на два случая:

  1. arr[j] меньше, чем arr[j-1]:
  • В этом случае алгоритм будет сравнивать arr[j] с arr[j-2], arr[j-3], …, arr[0] до тех пор, пока не найдет значение, меньшее, чем arr[j], и вставит arr [j] в правильном положении.
  • Все остальные элементы будут смещены вправо.
  • Следовательно, arr[0..j] будет отсортирован.

2. arr[j] больше, чем arr[j-1]:

  • По предположению индукции мы знаем, что arr[0..j-1] отсортировано, и если arr[j] больше, чем arr[j-1], то получаем, что arr[0..j] отсортировано .

Поскольку алгоритм корректен для первых j элементов массива, и мы предполагаем, что он корректен для первых j-1 элементов массива, мы можем сделать вывод, что алгоритм корректен для первых j+1 элементов массива .

Следовательно, алгоритм корректен для всех элементов массива.