Временная сложность упрощенной 3-сторонней сортировки разделов

Ниже приведен мой алгоритм, который представляет собой упрощенный алгоритм трехстороннего разделения Дейкстры для общего списка:

static <T extends Comparable> void dutchSort(List<T> list, int left, int right) {
    if (left >= right) return;

    T pivot = list.get(left);

    // smaller - index of the last element smaller than pivot value
    // equal - index of the last element equal to pivot value
    // larger - index of the first element larger than pivot value
    int smaller = left-1, equal = left, larger = right;

    // before sorting is completed, 'equal' is the current value
    // much like 'i' in a for-loop
    // O(N) time
    while (equal < larger) {
        if (list.get(equal).compareTo(pivot) < 0)
            Collections.swap(list, equal, ++smaller);
        else if (list.get(equal).equals(pivot))
            equal++;
        else
            Collections.swap(list, equal, --larger);
    }

    // recursively sort smaller subarray
    dutchSort(list, left, smaller+1);

    // recursively sort larger subarray
    dutchSort(list, equal, list.size());
}

Это O(1) пространства, и я думаю, что O(N^N) времени, но я не уверен. В сообщении Toptal о 3-сторонней быстрой сортировке говорится, что это O (N^2), но разница в том, что мой алгоритм гораздо более наивен. Мой мыслительный процесс таков: цикл while занимает O(N) времени, и в худшем случае (все N элементов различны?) проблема разбивается на N подмассивов размера 1.

Я попробовал основную теорему, но не был уверен ни в одном из значений переменных. Я думаю, что количество подзадач равно 2, каждый рекурсивный вызов уменьшает проблему в 2 раза, а объединение подзадач требует O (1) работы.

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

Верно ли время O(N^N)? И если да, то почему?

Спасибо большое :)


person A is for Ambition    schedule 26.04.2018    source источник
comment
Это определенно не пространство O (1), если вы считаете стек рекурсии. Стандартные ссылки говорят, что пространство равно O (log n). Что касается сложности во время выполнения, почему бы просто не увеличивать счетчик каждый раз в цикле while? Затем запустите алгоритм для выборки размеров массива и посмотрите, что у вас получится? Вы должны быть в состоянии довольно легко определить разницу между O (n ^ 2) и O (n ^ n).   -  person Jim Mischel    schedule 26.04.2018
comment
Конечно, это способ эмпирически определить время выполнения, но я надеялся найти более точное с математической точки зрения решение.   -  person A is for Ambition    schedule 27.04.2018
comment
Вы сказали, что думаете, что это n^n. Если эмпирические данные совпадают или расходятся, вы можете выяснить, почему. Работайте в обратном направлении от ответа, чтобы разработать формулу.   -  person Jim Mischel    schedule 28.04.2018
comment
Я не думаю, что это очень полезно или продуктивно. Меня не волнует ответ. Я хочу узнать, как найти время выполнения для алгоритмов, которые не являются стабильными, например. размеры подзадач могут варьироваться. Даже если я опытным путем получил правильную среду выполнения, что тогда? Эмпирический вывод среды выполнения не научит меня этому, потому что это именно то, что я не знаю, как делать.   -  person A is for Ambition    schedule 28.04.2018


Ответы (1)


Таким образом, цикл while равен O(n) при начальном вызове. Если мы допустим массив [1, 2, 3, 4, 5], то в первый раз через цикл list[equal] == pivot, и мы увеличиваем equal.

Второй и последующие разы в цикле list[equal] > pivot, поэтому мы уменьшаем larger и меняем местами с этим элементом. Когда цикл завершится, у вас будет equal=1, а smaller не изменится. Ваши рекурсивные вызовы становятся:

dutchSort(list, 0, 0)
dutchSort(list, 1, n)

Итак, один из предметов выпал.

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

Чтобы ваш алгоритм был O (N ^ N), ему пришлось бы сравнивать каждый элемент с каждым другим элементом несколько раз. Но этого не происходит, потому что на каждом уровне рекурсии вы разбиваете задачу на две части. Как только что-то разбивается на левую половину массива, его нельзя сравнивать с чем-то, что было перемещено в правую половину массива. Таким образом, в худшем случае каждый элемент сравнивается с любым другим элементом. Это будет O(N^2).

Когда все элементы равны, алгоритм равен O(N).

Я думаю, что сложность алгоритма определяется количеством уникальных элементов. Не похоже, что первоначальный порядок массива будет иметь какое-либо влияние.

person Jim Mischel    schedule 27.04.2018