1632
правки
Изменения
м
For '''for''' i = 1..n For '''for''' j = 1..(i - 1) '''if ''' P[j] > P[i] T[P[i]] = T[P[i]] + 1+Сложность данного алгоритма {{---}} $<tex>O(n^2)$</tex>. Уменьшить время работы можно используя алгоритм, похожий на [[Дерево отрезковСортировка_слиянием |сортировку слиянием.]] == Алгоритм построения за O(N log N) == Пусть дано разбиение перестановки на два списка, причём для каждого элемента дано число инверсий слева с элементами того же списка и известно, что все числа первого списка стоят левее всех чисел второго списка в исходной перестановке. ПостроениеБудем считать количество инверсий слева элементов обоих списков следующим образом: сливаем списки, аналогично [[Сортировка_слиянием |дерево отрезковсортировке слиянием.]] Если в результат нужно записать элемент первого списка, то все нерассмотренные элементы второго списка больше, следовательно, количество инверсий для этого элемента не меняется. Если в результат нужно записать элемент второго списка, то все нерассмотренные элементы первого списка больше его и стоят левее. Следовательно, количество инверсий для этого элемента следует увеличить на количество нерассмотренных элементов первого списка.
Получим из исходной Описанный алгоритм записывается в псевдокод следующим образом: <font color=green>// ''inverses_merge'' {{---}} процедура, сливающая два списка пар</font> <font color=green>// ''inverses_get'' {{---}} процедура, рекурсивно получающая таблицу инверсий для перестановки обратную и запишем её в массив $M$</font> '''def''' inverses_merge(ls1, ls2): result = [] it1, it2 = ''null'' '''while''' (it1 < ls1. Также заведём массив $S$ длиной $n$, инициализированный нулямиlength) '''and''' (it2 < ls2. После обработки $i$-го элемента будем вносить значение 1 в ячейку $Slength) '''if''' ls1[Mit1].item < ls2[iit2].item result.append(ls1[it1]$) it1++ '''else''' result. Обработку начинаем с последнего элемента и двигаемся к началуappend(item = ls2[it2]. Пустьitem, функция $Suminverses = ls2[it2].inverses + ls1.length - it1) it2++ '''while''' it1 < ls1.length result.append(ils1[it1])$ возвращает значение суммы элементов массива $S$ от 1 до $i$ it1++ '''while''' it2 < ls2. Тогда $i$-й элемент таблицы инверсий находится так:length $T result.append(ls2[iit2] ) it2++ '''return''' result '''def''' inverses_get(ls): '''if''' ls.length == Sum1 '''return''' [(Mitem = ls[i0], inverses = 0)] '''else''' '''return''' inverses_merge(inverses_get(ls.first_half), inverses_get(ls.second_half))$
Данная формула даёт правильный ответ, так как $S[M[i]]$ содержит единицу только в том случае, если мы уже обработали элемент, стоящий в $M[i]$-й позиции. Так как обработка идёт от больших чисел к меньшим, $Sum(M[i])$ выдаст нужное нам количество чисел, больших $i$ и стоящих в перестановке левее него.
Функция $Sum$ реализуется Сложность представленного алгоритма есть <tex>O(n\log n)</tex>. Алгоритм с такой же сложностью можно построить с помощью [[Дерево отрезковДерево_отрезков. Построение_Построение |дерева отрезков.]]. Каждое изменение массива и обращение к функции $Sum$ влечёт за собой $\log_2n$ операций. Таким образом получаем сложность алгоритма $O(n\log_2n)$
Приведём алгоритм восстановления с использованием [[Сортировка слиянием|сортировки слиянием]], имеющий сложность $O(n\log_2n)$.
Пусть $\alpha$ '''int''' bucket_sort('''vector<int>''' permutation): '''int''' max = число больше permutation.size и $\beta$ из которого можно извлечь целый квадратный корень '''int''' bucket = sqrt(max) '''int''' answer = 0<font color=green> // изначально кол- цепочки упорядоченных пар целых неотрицательных чисел $[m_1, n_1]\dots[m_k, n_k]$во инверсий</font> '''list<list<int>>''' bank(bucket) '''for''' i = 0 to permutation. Рассмотрим двоичную операцию $\circ$, рекурсивно определенную на парах таких цепочек следующим образом:size $ '''int''' pos = (permutation[m, ni]\alpha- 1)\circ/ (max / bucket) <font color=green>// Определяем в каком кармане должен лежать элемент</font> '''int''' newPosition = 0 '''while''' newPosition < bank[mpos].size '''and'', n'bank[pos][newPosition] < permutation[i]\beta)<font color=\left\{\begin{aligned}green>// идем до позиции где должен стоять элемент permutation[i] </font> newPosition++ answer += bank[pos].size - newPosition <font color=green>// ищем сколько инверсий эленент создает в своем кармане</font> bank[m,npos].insert(\alpha \circ (newPosition, permutation[mi]) <font color=green>// вставляем элемент в Карман на свою позицию </font> '-m, n']\beta)), m \le m',\\ [mfor', n'](([m-m'i = position + 1 to bucket -1, n<font color=green>// ищем сколько инверсий он создает с элементами в других карманах</font> answer += bank[i]\alpha) \circ \beta), m>m'.\\size \end{aligned} \right.$ '''return''' answer
Сопоставим каждому элементу таблицы инверсий его номер. Получится множество упорядоченных пар чисел $В разделе [[m_1, n_1Карманная сортировка | карманная сортировка]\dots[m_k, n_k]$, где $m_i$ {{---}} сам элементдоказывается, а $n_i$ {{---}} его номерчто она работает за линейное время. Разобьём данные элементы на пары и произведём с ними операцию $\circ$. Получим некоторое количество цепочек упорядоченных пар. Также разбиваем их на пары и производим операцию $\circ$. Так действуемЧто касается подсчета инверсий, пока не останется одна цепочка. Выписывая вторые элементы данных упорядоченных пар то в том порядке, приведенной реализации происходит [[Карманная сортировка | карманная сортировка]] в каком они представлены в цепочке, получим первоначальную перестановкуonline режиме и вся математическая часть подходит и под этот случай.
Цепочка наподобие $Следует отметить, что хотя подсчет с помощью [4, 4[Карманная сортировка | карманной сортировки]][1выполняется за линейное время,3]$ представляет "_ _ _ _ 4 _ 3 _ $но имеет очень большую константу поэтому подсчет инверсий рассматриваемый выше за <tex>O(n\infty$", где "_" означает пропускlog n)</tex> работает быстрее . Операция $\alpha \circ \beta$ вставляет пропуски и заполнения из $\beta$ на место пропусков в $\alpha$
* $[4, 1, 6, 3, 2, 2, 1, 1, 1, 0]$ - таблица инверсий.Данный алгоритм переписывается в код следующим образом:
* $[4,1]\circ[1,2], [6,3]\circ[3,4], [2,5]\circ[2,6], [1,7]\circ[1,8], [1,9]\circ[0,10]$
* $[1,2][2,1]\circ[3,4][2,3], [2,5][0,6]\circ[1,7][0,8], [0,10][0,9]$== Пример ==
* $[{| style = "border: 0px solid; background-color: gray; text-align: center; padding : 0" cellspacing = "2"| style = "background-color: white; padding: 3px 6px" |(5, 0)| style = "background-color: white; padding: 3px 6px" |(7, 0)| style = "background-color: white; padding: 3px 6px" |(1,0)| style = "background-color: white; padding: 3px 6px" |(3, 0)| style = "background-color: white; padding: 3px 6px" |(4, 0)| style = "background-color: white; padding: 3px 6px" |(6, 0)| style = "background-color: white; padding: 3px 6px" |(8, 0)| style = "background-color: white; padding: 3px 6px" |(2][, 0)|-|colspan = "2" style = "background-color: white; padding: 3px 6px"|(5,0), (7, 0)|colspan = "2" style = "background-color: white; padding: 3px 6px"|(1][, 0), (3,0)|colspan = "2" style = "background-color: white; padding: 3px 6px"|(4][, 0), (6, 0)|colspan = "2" style = "background-color: white; padding: 3px 6px"|'''(2, 1)''', (8,0)|-|colspan = "4" style = "background-color: white; padding: 3px 6px"|'''(1, 2)''', '''(3]\circ[1,2)''', (5, 0), (7][, 0)|colspan = "4" style = "background-color: white; padding: 3px 6px"|'''(2, 3)''', (4,5][0),(6][, 0), (8,0)|-|colspan = "8]" style = "background-color: white; padding: 3px 6px"|(1, 2), '''(2, 6)''', (3, 2), '''(4, 2)''', (5, [0), '''(6, 1)''', (7,10][0), (8,9]$0)|}
* $[{| style = "border: 0px solid; background-color: grey; text-align: center; padding : 0;" cellspacing = "2"|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>\bf{1}</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: #EEF; text-align: left; padding: 3px 6px"|пропускаем две свободных позиции и ставим <tex>\bf{1}</tex>|-|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>1</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>\bf{2}</tex>|colspan = "1" style = "background-color: #EEF; text-align: left; padding: 3px 6px"|пропускаем шесть свободных позиций и ставим <tex>\bf{2}</tex>|-|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>1</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>\bf{3}</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1," style = "background-color: white; padding: 3px 6px"|<tex>2][</tex>|colspan = "1" style = "background-color: #EEF; text-align: left; padding: 3px 6px"|пропускаем две свободных позиции и ставим <tex>\bf{3}</tex>|-|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0,7][</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>1</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>3</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>\bf{4}</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0,</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>2</tex>|colspan = "1" style = "background-color: #EEF; text-align: left; padding: 3px 6px"|пропускаем две свободных позиции и ставим <tex>\bf{4}</tex>|-|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>\bf{5][}</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0,</tex>|colspan = "1][0," style = "background-color: white; padding: 3px 6px"|<tex>1</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>3</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>4][</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0,6][</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0,8][</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>2</tex>|colspan = "1" style = "background-color: #EEF; text-align: left; padding: 3px 6px"|не пропускаем свободных позиции и ставим <tex>\bf{5}</tex>|-|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>5</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0,</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>1</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>3]</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>4</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>\circ[bf{6}</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0,10][</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>2</tex>|colspan = "1" style = "background-color: #EEF; text-align: left; padding: 3px 6px"|пропускаем одну свободную позицию и ставим <tex>\bf{6}</tex>|-|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>5</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>\bf{7}</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>1</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>3</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>4</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>6</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>0,9]$</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>2</tex>|colspan = "1" style = "background-color: #EEF; text-align: left; padding: 3px 6px"|не пропускаем свободных позиций и ставим <tex>\bf{7}</tex>|-|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>5</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>7</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>1</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>3</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>4</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>6</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>\bf{8}</tex>|colspan = "1" style = "background-color: white; padding: 3px 6px"|<tex>2</tex>|colspan = "1" style = "background-color: #EEF; text-align: left; padding: 3px 6px"|не пропускаем свободных позиций и ставим <tex>\bf{8}</tex>|}
* $[0,10][0,2][0,7][0,5][0,1][0,4][0,6][0,8][0,3][0,9]$== Источники информации ==
Получаем перестановку $[10, 2, 7, 5, 1, 4, 6, 8, 3, 9]$
= Источники =[[Категория: Дискретная математика и алгоритмы]]
* Д. Кнут - Искусство программирования, том 3.</wikitex>[[Категория: Комбинаторика ]]
rollbackEdits.php mass rollback
Пусть <wikitextex>Пусть $ P = (p_1,p_2,\dots,p_n)$ </tex> является [[Действие перестановки на набор из элементов, представление в виде циклов|перестановкой]] чисел $ <tex> 1, 2,\dots, n$</tex>.
{{Определение
|definition =
'''Инверсией''' (англ. ''inversion'') в перестановке $<tex>P$ </tex> называется всякая пара индексов $<tex>i, j$ </tex> такая, что $<tex>1\leqslant i<j\leqslant n$ </tex> и $<tex>P[i]>P[j]$</tex>.
}}
{{Определение
|definition =
'''Таблицей инверсий''' (англ. ''inversion table'') перестановки $ <tex> P $ </tex> называют такую последовательность $ <tex> T = (t_1,t_2,\dots,t_n)$</tex>, в которой $<tex>t_i$ </tex> равно числу элементов перестановки $ <tex> P $</tex>, стоящих в $ <tex> P $ </tex> левее числа $<tex>i$ </tex> и больших $<tex>i$</tex>.
}}
== Алгоритм построения за O(N<sup>2</sup>) ==
Таблицу инверсий тривиально построить по определению. Для каждого элемента перестановки считаем количество элементов, больших данного и стоящих в перестановке левее него.
Алгоритм построения в псевдокоде выглядит так:
T[1..n] = 0
== Алгоритм восстановления построения за O(N) ==
Для восстановления таблицы перестановки из построения таблицы инверсий создаем таблицу, которую будем расширятьза линейное время воспользуемся [[Карманная сортировка | карманной сортировкой]]. При [[Карманная сортировка | карманной сортировке]] нужно определить карман <tex>B</tex>, по мере добавления в неё чиселкоторый попадет текущий элемент. Добавляем в эту таблицу число $i$ (где $i$ от $n$ до 1) на позицию $k+1$, где $k$ - Затем найти число элементов в таблице инверсий на $i$-том местестарших карманах относительно <tex>B</tex>. Данный алгоритм довольно прост Потом аккуратно подсчитать количество элементов, больших текущего в реализации, но без использования дополнительных структур данных, имеет сложность $O(n^2)$, т. ккармане <tex>B</tex>. Карман <tex>A</tex> считается старшим для вставки кармана <tex>B</tex>, если любой элемент из <tex>A</tex> больше любого элемента в определённую позицию, требуется порядка $n$ перестановок элементовиз <tex>B</tex>.
== Пример Алгоритм восстановления == Для восстановления перестановки по таблицы инверсий <tex>T</tex> воспользуемся следующим соображением: единица стоит в перестановке на <tex>T_0</tex>-ом месте (индексируем элементы с нуля), так как остальные числа в перестановке больше единицы. Далее, если известны расположения всех чисел <tex>1, \dots, k</tex>, то число <tex>k + 1</tex> стоит на <tex>T_{k + 1}</tex>-ой ещё не занятой позиции: все числа, меньшие <tex>k + 1</tex> уже расставлены. Это рассуждение напрямую переписывается в код следующим образом: <font color=green>// ''j'' {{---}} счётчик пропущенных свободных позиций</font> <font color=green>// ''k'' {{---}} количество инверсий слева для элемента curr</font> <font color=green>// ''result'' {{---}} массив, в который записывается перестановка. Равенство элемента массива нулю обозначает, что эта позиция свободна.</font> '''def''' recover_straight(ls): n = ls.length result = array(0, n) curr = 1 '''for''' k '''in''' ls j = 0 '''for''' i = 0..(n - 1) '''if''' result[i] == 0 '''if''' j == k result[i] = curr '''break''' '''else:''' j++ curr++ '''return''' result Этот простой алгоритм имеет сложность <tex>O(n^2)</tex> — внутренний цикл делает до <tex>n</tex> итераций, внешний — ровно <tex>n</tex> итераций. Видно, что для восстановления нужно узнавать <tex>k</tex>-ую свободную позицию. Это можно делать с помощью [[Дерево_отрезков._Построение | дерева отрезков]] следующим образом: построим дерево отрезков для суммы на массиве из единиц. Единица в позиции означает, что данная позиция свободна. Чтобы найти <tex>k</tex>-ую свободную позицию, нужно спускаться (начиная с корня) в левое поддерево если сумма в нём больше, чем <tex>k</tex>, и в правое дерево иначе.
<font color=green>// ''build_segment_tree'' {{---}} строит дерево отрезков над массивом</font>
<font color=green>// ''node'' {{---}} вершина дерева</font>
<font color=green>// ''node.index'' {{---}} индекс соответствующего элемента в массиве для листа дерева</font>
'''def''' recover(inv):
n = inv.length
tree = build_segment_tree(array(n, 1))
result = array(n)
curr = 1
'''for''' k '''in''' inv
node = tree.root
'''while''' !node.is_leaf
'''if''' k < node.left.value
node = node.left
'''else'''
k -= node.left.value
node = node.right
result[node.index] = curr
node.add(-1)
curr++
'''return''' result
Этот алгоритм имеет сложность <tex>O(n \log n)</tex>: делается <tex>n</tex> итераций цикла, в которой происходит спуск по дереву высоты <tex>O(\log n)</tex> и один запрос на дереве отрезков. Таким образом, время работы алгоритма на каждой итерации есть <tex>O(\log n)</tex>.
Рассмотрим пример построения таблицы инверсий и восстановления перестановки по таблице инверсий. Пусть дана перестановка <tex>(5, 7, 1, 3, 4, 6, 8, 2)</tex>. Следующая таблица показывает работу алгоритма за <tex>O(n \log n)</tex>, на каждой строке один уровень рекурсии (на первой строке — самый глубокий). В скобках стоят пары: элемент перестановки, количество инверсий. Полужирным отмечены элементы, у которых обновилось значение количества инверсий на данном шаге.
Полученная таблица инверсий: <tex>(2, 6, 2, 2, 0, 1, 0, 0)</tex>. Восстановим перестановку по таблице инверсий, начиная с пустого массива.
== См. также ==
* [[Матричное представление перестановок]]
* [https://en.wikipedia.org/wiki/Permutation Wikipedia {{---}} Permutation]
* Д. Кнут - Искусство программирования, том 3 {{---}} 29-31 с.