Мы все виноваты в этом. Когда мы сталкиваемся с проблемой, требующей отсортировать массив, мы по умолчанию используем пузырьковую сортировку. Я знаю, что мы можем добиться большего!

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

Как мы видим, в среднем временная сложность быстрой сортировки равна n log от n, а в худшем случае сложность равна n в квадрате.

Если мы посетим каждый элемент только один раз, то у нас будет O (N).

У нас есть O (N²), когда для каждого элемента мы посещаем каждый другой элемент. Например, в случае пузырьковой сортировки каждый раз, когда мы добавляем новый элемент в список, мы должны не только сравнивать его с каждым элементом, но также сравнивать каждый элемент с ним.

Каждый раз, когда вы видите Log₂N, вы должны автоматически думать о подмножестве N элементов в списке. В алгоритме быстрой сортировки мы посещаем каждый элемент один раз. По этой причине перед журналом стоит буква N. Учитывая, что в среднем у нас примерно одинаковое количество элементов, которые больше или меньше, чем точка поворота, нам требуется проходить все меньше и меньше элементов, чтобы отсортировать список, поскольку мы продолжаем разбивать на основе текущей точки поворота. . По этой причине быстрая сортировка имеет временную сложность O (NLog₂N).

Пузырьковая сортировка

Предположим, у нас есть следующий список.

Начнем со сравнения первого элемента со следующим.

Поскольку второй элемент меньше первого, мы меняем их местами.

Затем мы перемещаем указатель на второй элемент и сравниваем его с третьим.

Поскольку третий элемент больше текущего, мы просто идем дальше.

9 больше 3. Поэтому мы их меняем местами.

Перемещаем указатель.

9 больше 8. Таким образом, мы их меняем местами.

Мы повторяем этот процесс, пока не пройдемся по всем индексам в списке.

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

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

Предположим, у нас есть тот же список, что и в предыдущем примере.

Мы начинаем с выбора одного числа в качестве точки поворота. Поворотом может быть что угодно, но в этом примере мы выбираем последний элемент в списке.

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

4 больше 2. Таким образом, он помещается в список справа.

Повторяем процесс для остальных элементов.

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

Поскольку в списке слева только один элемент, он уже отсортирован.

Мы используем последний элемент в списке как точку опоры.

Мы помещаем все числа больше 6 в один список и все числа меньше 6 в другой.

Перебираем оставшиеся списки.

Завершаем отсортированным списком.

Пример Python

Для начала мы создаем список из 1000 элементов от 0 до 100.

import random
number_of_elements = 1000
max_element_value = 100
min_element_value = 0
elements = []
for i in range(number_of_elements):
    number = random.randint(min_element_value, max_element_value)
    elements.append(number)

Мы определяем функцию для сортировки списка, используя алгоритм пузырьковой сортировки в качестве основы для сравнения.

def bubble_sort(elements):
    for i in range(len(elements)):
        for j in range(len(elements) - 1):
            if elements[j] > elements[j+1]:
                temp = elements[j]
                elements[j] = elements[j+1]
                elements[j+1] = temp
    return elements

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

import time
start_time = time.time()
sorted_elements = bubble_sort(elements)
print("--- %s seconds ---" % (time.time() - start_time))
--- 0.09066176414489746 seconds ---

Мы можем подтвердить, что мы действительно отсортировали массив, просмотрев первый и последний элементы.

print(sorted_elements[:10])
print(sorted_elements[-10:])
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]
[100, 100, 100, 100, 100, 100, 100, 100, 100, 100]

Реализуем алгоритм быстрой сортировки с использованием рекурсии.

def quick_sort(elements):
    
    if len(elements) <= 1:
        return elements
    
    pivot = elements.pop()
    greater_than_elements = []
    lesser_or_equal_than_elements = []
    for element in elements:
        if (element > pivot):
            greater_than_elements.append(element)
        else:
            lesser_or_equal_than_elements.append(element)
    return quick_sort(lesser_or_equal_than_elements) + [pivot] + quick_sort(greater_than_elements)

Наконец, мы сортируем список.

import time
start_time = time.time()
sorted_elements = quick_sort(elements)
print("--- %s seconds ---" % (time.time() - start_time))
--- 0.03184390068054199 seconds ---

Как мы видим, при сортировке массива из 1000 элементов у нас уже есть заметная разница во времени, затрачиваемом на пузырьковую сортировку и быструю сортировку. На первое уходит примерно треть времени на первое!