Как этот алгоритм сортировки Θ (n³), а не Θ (n²), в худшем случае?


52

Я только начал изучать структуры данных и алгоритмы, и мой ассистент дал нам следующий псевдокод для сортировки массива целых чисел:

void F3() {
    for (int i = 1; i < n; i++) {
        if (A[i-1] > A[i]) {
            swap(i-1, i)
            i = 0
        }
    }
}

Это может быть непонятно, но здесь n - размер массива, Aкоторый мы пытаемся отсортировать.

В любом случае, помощник преподавателя объяснил класс , что этот алгоритм в Θ(n3) время ( в худшем случае, я считаю), но независимо от того , сколько раз я иду через него с обратно-отсортированным массивом, кажется , для меня это должно быть Θ(n2) а не Θ(n3) .

Может ли кто-нибудь объяснить мне, почему это а не ?Θ(n3)Θ(n2)


Возможно, вас заинтересует структурированный подход к анализу ; Попробуйте найти доказательства самостоятельно!
Рафаэль

Просто осуществите это и измерьте, чтобы убедить себя. Массив с 10000 элементов в обратном порядке должен занимать много минут, а массив с 20000 элементов в обратном порядке - примерно в восемь раз дольше.
gnasher729

@ gnasher729 Вы не ошиблись, но мое решение другое: если вы попытаетесь доказать свою оценку, вы неизменно потерпите неудачу, что скажет вам что-то не так. (Конечно, можно сделать и то и другое. Построение / подбор определенно быстрее для отклонения гипотезы, но менее надежно . Пока вы делаете какие-то действия для формального / структурированного анализа, никакого вреда не будет. Полагаясь на графики, начинаются проблемы.)O(n2)
Рафаэль

1
из-за i = 0заявления
njzk2

Ответы:


60

Этот алгоритм можно переписать так

  1. Сканируйте, Aпока не найдете инверсию .
  2. Если вы найдете один, поменяйте местами и начните все сначала.
  3. Если нет, прекратить.

Теперь может быть не более инверсий, и вам нужно сканирование по линейному времени, чтобы найти каждое из них, так что наихудшее время выполнения - . Прекрасный пример обучения, поскольку он использует подход сопоставления с образцом, которому многие поддаются!(n2)Θ(n2)Θ(n3)

Примечание: нужно быть немного осторожнее: некоторые инверсии появляются рано, а некоторые поздно, поэтому само по себе нетривиально, что затраты складываются, как заявлено (для нижней границы). Вы также должны заметить, что свопы никогда не вводят новые инверсии. Более подробный анализ случая с обратно отсортированным массивом приведет к чему-то вроде квадратичного случая формулы Гаусса.

Как удачно комментирует @ gnasher729, легко увидеть, что наихудшее время выполнения - это , проанализировав время выполнения при сортировке ввода (хотя этот вход, вероятно, не в худшем случае).Ω(n3)[1,2,,n,2n,2n1,,n+1]

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


14
Если вы возьмете массив, в котором первая половина состоит из чисел от 1 до n / 2 в порядке возрастания, а вторая половина - от n до n / 2 + 1 в обратном порядке, то очевидно, что вам нужно как минимум n / 2 шагов, чтобы найти каждую инверсию, и их будет около (n / 2) ^ 2/2. И это, скорее всего, не худший случай.
gnasher729

@AnthonyRossello Это стандартный результат (в комбинаторике перестановок). Короче говоря, посчитайте количество инверсий в обратно отсортированном массиве (это очевидно, что это худший случай?); это сумма Гаусса.
Рафаэль

Следует помнить, что, несмотря ни на что, частичные суммы всегда являются , это просто коэффициент, который быстро падает: (обратите внимание на довольно большой коэффициент ). Проблема в том, что не заботится о коэффициентах. Θ(nα)Θ(nα+1)k=0nkα1α+1nα+11α+1Θ
лет»

2
@yo 'И это касается ответа (или вопроса) как?
Рафаэль

7

Альтернативный способ думать об этом - это то, каким будет максимальное значение iдо его сброса. Это, как выясняется, упрощает рассуждение о том, как предыдущий порядок сортировки Aвлияет на время выполнения алгоритма.

В частности, iобратите внимание, что, когда устанавливает новое максимальное значение, назовем его N, массив [A[0], ..., A[N-1]]сортируется в порядке возрастания.

Так что же происходит, когда мы добавляем элемент A[N]в микс?

Математика:

Ну, допустим, он помещается в положение . Затем нам нужно итераций цикла (которые я буду обозначать ), чтобы переместить его на , итераций, чтобы переместить на , и вообще:pNNstepsN1N+(N1)N2

stepsN(pN)=N+(N1)+(N2)++(pN+1)=12(N(N+1)pN(pN+1))

Для случайно отсортированного массива принимает равномерное распределение по для каждого , с:pN{0,1,,N}N

E(stepsN(pN))=a=1NP(pN=a)stepsN(a)=a=1N1N12(N(N+1)a(a+1))=12(N(N+1)13(N+1)(N+2))=13(N21)=Θ(N2)

сумма может быть показана с использованием формулы Фолхабера или ссылки Wolfram Alpha внизу.

Для обратно отсортированного массива, для всех , и мы получаем:pN=0N

stepsN(pN)=12N(N+1)

точно, принимая строго дольше, чем любое другое значение .pN

Для уже отсортированного массива и , причем члены младшего порядка становятся актуальными.pN=NstepsN(pN)=0

Общее время:

Для того, чтобы получить общее время, мы подведем шаги по всему . (Если бы мы были очень осторожны, мы суммировали бы свопы, а также итерации цикла, и позаботились бы о начальных и конечных условиях, но довольно легко увидеть, что они не вносят свой вклад в сложность в большинстве случаев) ,N

И снова, используя линейность ожидания и формулу Фолхабера:

Expected Total Steps=E(N=1nstepsN(pN))=N=1nE(stepsN(pN))=Θ(n3)

Конечно, если по какой-то причине не является (например, распределение массивов, на которые мы смотрим, уже очень близко к сортировке), то это не всегда нужно быть так. Но для этого очень специфичные дистрибутивы на !stepsN(pN)Θ(N2)pN

Соответствующее чтение:


@Raphael - спасибо за предложенные улучшения, я добавил немного больше деталей. Ну, случайные переменные - это (из , множество упорядочений ), поэтому ожидания технически сделаны надpiΩAΩ
David E

Разные ; Я имел в виду Ландау. Ω
Рафаэль

3

Отказ от ответственности:

Это не доказательство (кажется, что некоторые люди думают, что я разместил это, как будто это было). Это лишь небольшой эксперимент, который ОП может провести, чтобы разрешить свои сомнения относительно назначения:

Независимо от того, сколько раз я проходил через массив с обратной сортировкой, мне кажется, что это должно быть а не .Θ(n2)Θ(n3)

С таким простым кодом различие между и не должно быть трудно обнаружить, и во многих практических случаях это полезный подход для проверки догадок или корректировки ожиданий.Θ(n2)Θ(n3)


@Raphael уже ответил на ваш вопрос, но только для удовольствия, подгоняя выходные данные этой программы к используя этот сценарий gnuplot, сообщил значения экспоненты и и создал следующие графики ( первая - это нормальная шкала, а вторая - шкала log-log):f(x)=axb+cx2.997961668332222.99223727692339

обычный loglog

Я надеюсь, что это помогает¨


2
Вы можете приспособить любую функцию к этим значениям. Смотрите также здесь .
Рафаэль

3
@Raphael Если вы не хотите придираться таким образом, то нет, вы не можете уместить ни одну функцию (например, вы не сможете уместить постоянную функцию с любой разумной точностью). Это не доказательство, но уже есть ответ, который дает набросок. Что касается полезности, я могу процитировать ваш собственный пост, на который вы ссылались: «Я должен согласиться, что это очень полезный подход, который даже иногда недоиспользуется». Более того, ФП сказал, что, по его мнению, это должно быть а не , так почему бы не поэкспериментировать и посмотреть, была ли его догадка верной? Прод. Θ(n2)Θ(n3)
dtldarek

2
Это свидетельствует о том, что алгоритм является но возникает вопрос, почему . Требуется объяснение феномена, а не его подтверждение. Θ(n3)
Дэвид Ричерби

2
@DavidRicherby Значит ли это, что этот ответ бесполезен?
dtldarek

3
@Magicsowon Это сайт вопросов и ответов, а не форум. Мы ищем ответы на вопрос, а не обсуждение вокруг него.
Дэвид Ричерби

3

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

array a[10] = {10,8,9,6,7,4,5,2,3,0,1}

Ваш алгоритм делает следующее

Scan(1) - Swap (10,8) => {8,10,9,6,7,4,5,2,3,0,1}  //keep looking at "10"
Scan(2) - Swap (10,9) => {8,9,10,6,7,4,5,2,3,0,1}
...
Scan(10) - Swap(10,1) => {8,9,6,7,4,5,2,3,0,1,10}

По сути, он перемещается в конец массива, являющийся самым высоким элементом, и при этом он начинает повторы при каждом сканировании, эффективно делая O(n^2)перемещения ... только для этого одного элемента. Тем не менее, есть n элементов, поэтому нам придется повторить это nвремя. Это не формальное доказательство, но помогает понять «неформальным» образом, почему время выполнения O(n^3).


4
Что это добавляет к другим ответам? Объяснение того, что делает алгоритм, уже было дано, и ваши соображения относительно времени выполнения в лучшем случае отрывочны. (Наихудший случай не ведет себя линейно!)
Рафаэль

2
Иногда имеет смысл объяснять одну и ту же идею несколькими способами (с формализмом; с простым примером «прокачать интуицию»), особенно когда человек, задающий вопрос, является новичком в этой области. Поэтому мне кажется, что это добавляет то, что он представлен таким образом, что может помочь интуиции.
DW

Так как я получил ответ на мой комментарий во флаге (не делайте этого!): «В худшем случае не ведет себя линейно!» - Я имею в виду алгебраические свойства оператора наихудшего случая. Грубо говоря, вы используете WorstCase (1 + ... + n) "=" WorstCase (1) + ... + WorstCase (n), но эта идентификация не сохраняется.
Рафаэль

1
Я новичок в этой области, и предоставление объяснения с конкретным, изложенным примером определенно помогло мне получить представление о проблеме. Теперь принятое решение имеет больше смысла для меня.
vaer-k

0

Логика, кажется, сортирует элементы в массиве в порядке возрастания.

Предположим, что наименьшее число находится в конце массива (a [n]). Для того, чтобы он пришел в нужное место - (n + (n-1) + (n-2) + ... 3 + 2 + 1) операции не требуется. = O (n2).

Для одного элемента в массиве требуется O (n2) ops. Итак, для nements это O (n3).


5
Что это добавляет к другим ответам? Объяснение того, что делает алгоритм, уже было дано, и ваши соображения относительно времени выполнения в лучшем случае отрывочны. (Наихудший случай не ведет себя линейно!)
Рафаэль

Отличное объяснение. Это обеспечивает другой, более интуитивный взгляд на проблему, не объясненный в других ответах. (Не говоря уже об очень коротком и простом для понимания.)
2501

1
@ 2501 Нет, это неправильно. Попробуйте использовать эту «интуицию» в алгоритме Дейкстры, и вы получите квадратичное время выполнения (по числу узлов), что неверно.
Рафаэль

@ Рафаэль Нет, это правильно, как объясняется в ответе. Это объяснение работает для этого алгоритма, а не для других. Хотя это может быть неправильно для них, это утверждение не доказывает, что это неправильно для этого.
2501

@ Рафаэль Я не понял объяснения в принятом ответе. Итак, я решил это и попытался объяснить это в простых терминах без каких-либо технических терминов. Итак, это для таких участников, как я, которые не могли понять принятый ответ. Я рад, что кто-то нашел это полезным.
..
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.