Практическая работа 1 пара

Практическое занятие №1 «Оценка сложности алгоритмов сортировки»

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

Цель работы

На практике сравнить алгоритмы сортировки не по субъективному ощущению «быстро/медленно», а по количеству выполняемых операций и скорости роста затрат при увеличении размера массива.

Главная идея: нас интересует не точное число миллисекунд на одном компьютере, а то, как быстро растёт объём работы при росте n.

Что нужно знать перед началом

ОбозначениеСмысл
O(1)Количество работы почти не зависит от размера входных данных.
O(n)При увеличении n примерно в 2 раза объём работы растёт примерно в 2 раза.
O(n log n)Растёт быстрее линейного, но значительно медленнее квадратичного.
O(n²)При увеличении n в 2 раза объём работы может вырасти примерно в 4 раза.

Исходный код

Создайте файл sorting_complexity.py и вставьте код:

from random import randint
from timeit import repeat


def bubble_sort(data):
    a = data.copy()
    comparisons = 0

    for i in range(len(a) - 1):
        swapped = False

        for j in range(len(a) - 1 - i):
            comparisons += 1

            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
                swapped = True

        if not swapped:
            break

    return a, comparisons


def insertion_sort(data):
    a = data.copy()
    comparisons = 0

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

        while j >= 0:
            comparisons += 1

            if a[j] > key:
                a[j + 1] = a[j]
                j -= 1
            else:
                break

        a[j + 1] = key

    return a, comparisons


def merge_sort(data):
    comparisons = 0

    def sort(a):
        nonlocal comparisons

        if len(a) <= 1:
            return a

        mid = len(a) // 2
        left = sort(a[:mid])
        right = sort(a[mid:])

        result = []
        i = j = 0

        while i < len(left) and j < len(right):
            comparisons += 1

            if left[i] <= right[j]:
                result.append(left[i])
                i += 1
            else:
                result.append(right[j])
                j += 1

        result.extend(left[i:])
        result.extend(right[j:])
        return result

    return sort(data.copy()), comparisons


def measure(func, data):
    times = repeat(lambda: func(data), repeat=5, number=1)
    return min(times)


algorithms = {
    "Пузырьковая": bubble_sort,
    "Вставками": insertion_sort,
    "Слиянием": merge_sort,
}

sizes = [100, 500, 1000]

for n in sizes:
    random_data = [randint(0, 100000) for _ in range(n)]

    print(f"\nРазмер массива: {n}")

    for name, func in algorithms.items():
        _, comparisons = func(random_data)
        t = measure(func, random_data)

        print(
            f"{name:12} | "
            f"сравнений: {comparisons:8} | "
            f"время: {t:.6f} с"
        )

Ход работы

  1. Запустите программу без изменений и убедитесь, что все три алгоритма работают.
  2. Заполните таблицу для случайных массивов размером 100, 500 и 1000 элементов.
  3. Для n = 1000 отдельно проверьте три вида входных данных:
    • уже отсортированный массив: list(range(n));
    • массив в обратном порядке: list(range(n, 0, -1));
    • случайный массив.
  4. Сравните, как меняется число сравнений у пузырьковой сортировки и сортировки вставками в зависимости от исходного порядка.
  5. Сравните рост затрат при переходе от 100 к 1000 элементам. Определите класс сложности каждого алгоритма.
АлгоритмnТип входных данныхСравненийВремя, с
100случайные
500случайные
1000случайные
1000отсортированные
1000обратный порядок

Задания для анализа

  1. Почему два алгоритма с классом O(n²) могут показывать разное время на одинаковом массиве?
  2. Как изменится объём работы алгоритма O(n²), если размер массива увеличить примерно в 10 раз?
  3. Почему сортировка вставками работает особенно хорошо на почти отсортированных данных?
  4. Почему измерение времени само по себе не является доказательством класса сложности?
  5. Какой из трёх алгоритмов лучше масштабируется при больших n? Обоснуйте через характер роста, а не одной цифрой времени.

Что показать преподавателю

Ориентир по времени

10 минут — разбор задания; 45–50 минут — код и измерения; 15 минут — анализ таблиц; 5 минут — вывод. Если пара длится 90 минут, оставшееся время используйте для дополнительного задания.

Дополнительное задание

Добавьте sorted(data) и сравните встроенную сортировку Python с учебными реализациями. Не пытайтесь определять её сложность только по одному замеру: сформулируйте лишь экспериментальный вывод.