Подтвердить что ты не робот

Каковы правила для "Ω (n log n) барьера" для сортировки алгоритмов?

Я написал простую программу, которая сортирует в O (n). Это сильно неэффективно, но это не так.

Он использует принцип для сортировки:

public class NLogNBreak {
    public static class LinkedListBack {
        public LinkedListBack(int val){
            first = new Node();
            first.val = val;
        }
        public Node first = null;
        public void insert(int i){
            Node n = new Node();
            n.val = i;
            n.next = first;
            first = n;
        }
    }

    private static class Node {
        public Node next = null;
        public int val;
    }

    //max > in[i] > 0
    public static LinkedListBack[] sorted(int[] in, int max){
        LinkedListBack[] ar = new LinkedListBack[max + 1];
        for (int i = 0; i < in.length; i++) {
            int val = in[i];
            if(ar[val] == null){
                ar[val] = new LinkedListBack(val);
            } else {
                ar[val].insert(val);
            }
        }
        return ar;
    }
}

То есть, это считается как-то вроде O (n), хотя он возвращает результат в фанк-формате?

4b9b3361

Ответ 1

Чтобы прямо ответить на ваш вопрос:

  • Ваш алгоритм сортировки технически не O (n), а O (n + max), так как вам нужно создать массив размера max, который принимает время O (max).
  • Это не проблема; на самом деле, это частный случай известного алгоритма сортировки, который разрушает барьер & Omega; (n log n).

Итак, что это за & Omega; (n log n) барьер? От куда это? И как вы его нарушаете?

The & Omega; (n log n) Барьер

Барьер & Omega; (n log n) - это информационно-теоретическая нижняя граница скорости среднего хода любого алгоритма сортировки на основе сравнения. Если единственные операции, которые вам разрешено применять к элементам массива, чтобы их отличить, - это выполнить какое-то сравнение, тогда ваш алгоритм сортировки не может сделать лучше, чем & Omega; (n log n) в среднем случае.

Чтобы понять, почему это так, подумайте о состоянии алгоритма в любой момент его выполнения. По мере выполнения алгоритма он может получить некоторую информацию о том, как упорядочивались элементы ввода. Скажем, если алгоритм имеет некоторый набор информации X об исходном упорядочении входных элементов, то алгоритм находится в состоянии X.

Суть аргумента & Omega; (n log n) (и несколько связанных аргументов, как я расскажу позже) заключается в том, что алгоритм должен иметь возможность попасть в большое количество разных состояний на основе того, что ввод есть. Предположим теперь, что вход в алгоритм сортировки представляет собой массив из n различных значений. Поскольку алгоритм не может ничего рассказать об этих элементах, отличных от того, как они упорядочены, на самом деле не имеет значения, какие значения сортируются. Все, что имеет значение, - относительный порядок этих n элементов относительно друг друга.

Теперь для ключевого шага - предположим, что существуют f (n) уникальные способы упорядочения n входных элементов и предположим, что наш алгоритм сортировки не может попасть в по крайней мере f (n) разные состояния. Если это так, то должны быть два разных порядка элементов в массиве, которые алгоритм всегда объединяет вместе в одно и то же состояние. Если это произойдет, то алгоритм сортировки не может правильно правильно отсортировать оба из двух входных массивов. Причиной этого является то, что, поскольку алгоритм обрабатывает два массива одинаково, любые шаги, которые он использует для упорядочения элементов первого массива, будут такими же, как шаги, которые он использует для изменения порядка второго массива. Поскольку два массива не совпадают, должен быть по крайней мере один элемент, который будет неуместным в одном из двух случаев. Следовательно, мы знаем, что алгоритм сортировки должен иметь возможность войти в f (n) разных состояний.

Но как алгоритм может попасть в эти разные состояния? Ну, подумайте об этом. Первоначально алгоритм не имеет никакой информации о упорядочении элементов. Когда он делает свое первое сравнение (например, между элементами A [i] и A [j]), алгоритм может попасть в одно из двух состояний: одно, где A [i] A [j] и один, где A [i] > A [j]. В более общем плане, любое сравнение, которое делает алгоритм, может в лучшем случае поставить алгоритм в одно из двух новых состояний на основе результата сравнения. Поэтому мы можем думать о большой двоичной структуре дерева, описывающей состояния, в которых может быть алгоритм, - каждое состояние имеет до двух детей, описывающих, в каком состоянии находится алгоритм, на основе результата проведенного сравнения. Если мы берем любой путь от корня дерева до листа, мы получаем серию сравнений, которые в конечном итоге получают алгоритм на конкретном входе. Чтобы отсортировать как можно быстрее, мы хотим сделать максимально возможное количество сравнений, и поэтому мы хотим, чтобы эта древовидная структура имела наименьшую возможную высоту.

Теперь мы знаем две вещи. Во-первых, мы можем думать обо всех состояниях, которые алгоритм может получить как двоичное дерево. Во-вторых, это двоичное дерево должно иметь по крайней мере f (n) различные узлы в нем. Учитывая это, наименьшее возможное бинарное дерево, которое мы можем построить, должно иметь высоту не менее & Omega; (log f (n)). Это означает, что если существуют f (n) различные возможные способы упорядочения элементов массива, , мы должны сделать сравнения по крайней мере в & Omega; (log f (n)) в среднем, так как в противном случае мы можем "t попадают в достаточно разные состояния.

Чтобы завершить доказательство того, что вы не можете бить & Omega; (n log n), обратите внимание, что если в массиве есть n отдельных элементов, то есть n! различные возможные способы упорядочения элементов. используя приближение Стирлинга, мы имеем log n!= & Omega; (n log n), и поэтому мы должны сделать не менее & Omega; (n log n) сравнения в среднем случае для сортировки входной последовательности.

Исключения из правила

В том, что мы только что видели выше, мы увидели, что если у вас есть n элементов массива, которые все различны, вы не можете сортировать их со сравнением сортировки быстрее, чем & Omega; (n log n). Однако это исходное предположение не обязательно справедливо. Многие массивы, которые мы хотели бы отсортировать, могут содержать в себе дублированные элементы. Например, предположим, что я хочу сортировать массивы, которые состоят исключительно из нулей и единиц, таких как этот массив здесь:

 0 1 0 1 1 1 0 0 1 1 1

В этом случае неверно, что существует n! различные массивы нулей и длины n. На самом деле их всего 2 n. Из нашего вышеизложенного результата это означает, что мы должны иметь возможность сортировать в & Omega; (log 2 n) = & Omega; (n) время, используя алгоритм сортировки на основе сравнения. На самом деле мы абсолютно можем это сделать; здесь набросок того, как мы это сделаем:

  • Посмотрите на первый элемент.
  • Скопировать все элементы, меньшие, чем первый элемент, в массив с именем "less"
  • Скопировать все элементы, равные первому элементу, в массив с именем 'equal'
  • Скопировать все элементы, которые больше, чем первый элемент, в массив с именем "больше"
  • Объедините все три из этих массивов в порядке меньшего, равного, большего.

Чтобы увидеть, что это работает, если 0 - наш первый элемент, то массив "меньше" будет пустым, "равный" массив будет иметь все нули, а "большой" массив будет иметь все те же. Затем они объединяют все нули перед всеми. В противном случае, если 1 является нашим первым элементом, тогда массив less будет содержать нули, массив equal будет содержать те, а массив greater будет пустым. Таким образом, их конкатенация - все нули, за которыми следуют все, как требуется.

На практике вы не использовали бы этот алгоритм (вы бы использовали сортировку, как описано ниже), но это показывает, что вы действительно можете бить & Omega; (n log n) с помощью алгоритма сравнения, если количество возможных входов в алгоритм невелико.

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

Сортировка без сравнения

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

Например, если вы знаете, что входные значения выводятся из юниверса, который имеет только | U | элементов в нем, то вы можете сортировать по времени O (n + | U |) с помощью умного алгоритма. Начните с создания | U | различные ведра, в которые мы можем поместить элементы из исходного массива. Затем итерации по массиву и распределение всех элементов массива в соответствующее ведро. Наконец, посетите каждый из ведер, начиная с ведра, держащего копии самого маленького элемента и заканчивая ведром, содержащим копии самого большого элемента, а затем объединяйте все значения, которые вы найдете. Например, посмотрим, как сортировать массивы, состоящие из значений 1 - 5. Если у нас есть этот начальный массив:

1 3 4 5 2 3 2 1 4 3 5

Затем мы можем поместить эти элементы в ведра следующим образом:

Bucket     1  2  3  4  5
           -------------
           1  2  3  4  5
           1  2  3  4  5
                 3

Итерация по ковшим и объединение их значений вместе дает следующее:

1 1 2 2 3 3 3 4 4 5 5

который, безусловно, является отсортированной версией нашего исходного массива! Время выполнения - это время O (n), чтобы перейти и распределить исходные элементы массива в ведрах, а затем время O (n + | U |) для итерации по всем ковшим, которые объединяют элементы. Заметим, что если | U | = O (n), это выполняется в O (n) времени, нарушая барьер сортировки & Omega; (n log n).

Если вы сортируете целые числа, вы можете сделать гораздо лучше, чем это, используя radix sort, который работает в O (n lg | U |). Если вы имеете дело с примитивными int s, lg | U | обычно 32 или 64, так что это очень быстро. Если вы хотите внедрить особенно сложную структуру данных, вы можете использовать van Emde Boas Tree для сортировки целых чисел от 0 до U - 1 в время O (n lg lg U), опять же, используя тот факт, что целые числа состоят из групп бит, которые можно манипулировать в блоках.

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

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

Надеюсь, это поможет!

Ответ 2

Это называется Radix Sort, и да, он разбивает барьер nlog (n), который является только барьером на Comparison Model. На странице wikipedia, связанной для модели сравнения, вы можете увидеть список видов, которые ее используют, и несколько, которые этого не делают.

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

Обычно сортировка радикса выполняется по одному байту или полубайт за раз, чтобы уменьшить количество ковшей. См. Статью по Википедии, или найдите дополнительную информацию.

Кроме того, вы можете сортировать отрицательные числа и выделять память для используемых в нем ведер, чтобы улучшить его.