Таблица инверсий — различия между версиями
Dans (обсуждение | вклад) м (Необходимо сравнивать со значением в левом поддереве) |
6yry6e (обсуждение | вклад) |
||
Строка 3: | Строка 3: | ||
{{Определение | {{Определение | ||
|definition = | |definition = | ||
− | '''Инверсией''' в перестановке <tex>P</tex> называется всякая пара индексов <tex>i, j</tex> такая, что <tex>1\leqslant i<j\leqslant n</tex> и <tex>P[i]>P[j]</tex>. | + | '''Инверсией''' (англ. ''inversion'') в перестановке <tex>P</tex> называется всякая пара индексов <tex>i, j</tex> такая, что <tex>1\leqslant i<j\leqslant n</tex> и <tex>P[i]>P[j]</tex>. |
}} | }} | ||
{{Определение | {{Определение | ||
|definition = | |definition = | ||
− | '''Таблицей инверсий''' перестановки <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>. | + | '''Таблицей инверсий''' (англ. ''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>) == |
Таблицу инверсий тривиально построить по определению. Для каждого элемента перестановки считаем количество элементов, больших данного и стоящих в перестановке левее него. | Таблицу инверсий тривиально построить по определению. Для каждого элемента перестановки считаем количество элементов, больших данного и стоящих в перестановке левее него. | ||
Строка 19: | Строка 19: | ||
if P[j] > P[i] | if P[j] > P[i] | ||
T[P[i]] = T[P[i]] + 1 | T[P[i]] = T[P[i]] + 1 | ||
− | Сложность данного алгоритма {{---}} <tex>O(n^2)</tex>. Уменьшить время работы можно используя алгоритм, похожий на сортировку слиянием. | + | Сложность данного алгоритма {{---}} <tex>O(n^2)</tex>. Уменьшить время работы можно используя алгоритм, похожий на [[Сортировка_слиянием |сортировку слиянием.]] |
− | + | == Алгоритм построения за O(N log N) == | |
− | Если в результат нужно записать элемент первого списка, то все нерассмотренные элементы второго списка больше, следовательно, количество инверсий для этого элемента не меняется. Если в результат нужно записать элемент второго списка, то все нерассмотренные элементы первого списка больше его и стоят левее. Следовательно, количество инверсий для этого элемента следует увеличить на | + | Пусть дано разбиение перестановки на два списка, причём для каждого элемента дано число инверсий слева с элементами того же списка и известно, что все числа первого списка стоят левее всех чисел второго списка в исходной перестановке. Будем считать количество инверсий слева элементов обоих списков следующим образом: сливаем списки, аналогично [[Сортировка_слиянием |сортировке слиянием.]] |
+ | |||
+ | Если в результат нужно записать элемент первого списка, то все нерассмотренные элементы второго списка больше, следовательно, количество инверсий для этого элемента не меняется. Если в результат нужно записать элемент второго списка, то все нерассмотренные элементы первого списка больше его и стоят левее. Следовательно, количество инверсий для этого элемента следует увеличить на количество нерассмотренных элементов второго списка. | ||
Описанный алгоритм записывается в псевдокод следующим образом: | Описанный алгоритм записывается в псевдокод следующим образом: | ||
− | + | <font color=green>// <tex>inverses\_merge</tex> {{---}} процедура, сливающая два списка пар</font> | |
− | def | + | <font color=green>// <tex>inverses\_get</tex> {{---}} процедура, рекурсивно получающая таблицу инверсий для перестановки</font> |
− | result = [] | + | '''def''' <tex>\mathtt{inverses\_merge(ls1, ls2)}:</tex> |
− | + | <tex>\mathtt{result}\ = [ ]</tex> | |
− | while ( | + | <tex>it\textit{1}, it\textit{2}\ =</tex> ''null'' |
− | + | '''while''' <tex>(it\textit{1}</tex> <tex><</tex> <tex>\mathtt{ls1.length})</tex> '''and''' <tex>(it\textit{2}</tex> <tex><</tex> <tex>\mathtt{ls2.length}):</tex> | |
− | result.append(ls1[ | + | '''if''' <tex>\mathtt{ls1}[it\textit{1}]\mathtt{.item}</tex> <tex><</tex> <tex>\mathtt{ls2}[it\textit{2}]\mathtt{.item}:</tex> |
− | + | <tex>\mathtt{result.append}(\mathtt{ls1}[it\textit{1}])</tex> | |
− | else: | + | <tex>it\textit{1}++</tex> |
− | result.append(item = ls2[ | + | '''else:''' |
− | + | <tex>\mathtt{result.append}(item\ =</tex> <tex>\mathtt{ls2}[it\textit{2}]\mathtt{.item}, inverses\ =</tex> <tex>\mathtt{ls2}[it\textit{2}]\mathtt{.inverses}</tex> <tex>+</tex> <tex>\mathtt{ls1.length}</tex> <tex>-</tex> <tex>it\textit{1})</tex> | |
− | while ( | + | <tex>it\textit{2}++</tex> |
− | result.append(ls1[ | + | '''while''' <tex>(it\textit{1}</tex> <tex><</tex> <tex>\mathtt{ls1.length}):</tex> |
− | + | <tex>\mathtt{result.append}(\mathtt{ls1}[it\textit{1}])</tex> | |
− | while ( | + | <tex>it\textit{1}++</tex> |
− | result.append(ls2[ | + | '''while''' <tex>(it\textit{2}</tex> <tex><</tex> <tex>\mathtt{ls2.length}):</tex> |
− | + | <tex>\mathtt{result.append}(\mathtt{ls2}[it\textit{2}])</tex> | |
− | return result | + | <tex>it\textit{2}++</tex> |
+ | '''return''' <tex>\mathtt{result}</tex> | ||
− | def | + | '''def''' <tex>\mathtt{inverses\_get(ls)}:</tex> |
− | if | + | '''if''' <tex>\mathtt{ls.length}\ == 1:</tex> |
− | return [(item = ls[0], inverses = 0)] | + | '''return''' <tex>[(item = \mathtt{ls[0]}, inverses = 0)]</tex> |
− | else: | + | '''else:''' |
− | return | + | '''return''' <tex>\mathtt{inverses\_merge(inverses\_get(ls.first\_half), inverses\_get(ls.second\_half))}</tex> |
− | |||
− | |||
− | Сложность представленного алгоритма есть <tex>O(n\log n)</tex>. Алгоритм с такой же сложностью можно построить с помощью дерева отрезков. | + | Сложность представленного алгоритма есть <tex>O(n\log n)</tex>. Алгоритм с такой же сложностью можно построить с помощью [[Дерево_отрезков._Построение | дерева отрезков.]] |
== Алгоритм восстановления == | == Алгоритм восстановления == | ||
− | Для восстановления перестановки по таблицы инверсий <tex>T</tex> воспользуемся следующим соображением: единица стоит на <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>// <tex>j</tex> {{---}} счётчик пропущенных свободных позиций</font> | ||
+ | <font color=green>// <tex>k</tex> {{---}} количество инверсий слева для элемента curr</font> | ||
+ | <font color=green>// <tex>result</tex> {{---}} массив, в который записывается перестановка. Равенство элемента массива нулю обозначает, что эта позиция свободна.</font> | ||
+ | '''def''' <tex>\mathtt{recover\_straight(ls)}:</tex> | ||
+ | <tex>n\ = \mathtt{ls.length}</tex> | ||
+ | <tex>\mathtt{result}\ = \mathtt{array}(0, n)</tex> | ||
+ | <tex>curr\ = 1</tex> | ||
+ | '''for''' <tex>k \in \mathtt{ls}:</tex> | ||
+ | <tex>j\ = 0</tex> | ||
+ | '''for''' <tex>(i\ = 0, i < n, i++):</tex> | ||
+ | '''if''' <tex>\mathtt{result}[i]\ == 0:</tex> | ||
+ | '''if''' <tex>j\ == k:</tex> | ||
+ | <tex>\mathtt{result}[i]\ = curr</tex> | ||
+ | '''break''' | ||
+ | '''else:''' | ||
+ | <tex>j++</tex> | ||
+ | <tex>curr++</tex> | ||
+ | '''return''' <tex>\mathtt{result}</tex> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
Этот простой алгоритм имеет сложность <tex>O(n^2)</tex> — внутренний цикл делает до <tex>n</tex> итераций, внешний — ровно <tex>n</tex> итераций. | Этот простой алгоритм имеет сложность <tex>O(n^2)</tex> — внутренний цикл делает до <tex>n</tex> итераций, внешний — ровно <tex>n</tex> итераций. | ||
− | Видно, что для восстановления нужно узнавать <tex>k</tex>-ую свободную позицию. Это можно делать с помощью дерева отрезков следующим образом: построим дерево отрезков для суммы на массиве из единиц. Единица в позиции означает, что данная позиция свободна. Чтобы найти <tex>k</tex>-ую свободную позицию, нужно спускаться (начиная с корня) в левое поддерево если сумма в нём больше, чем <tex>k</tex>, и в правое дерево иначе. | + | Видно, что для восстановления нужно узнавать <tex>k</tex>-ую свободную позицию. Это можно делать с помощью [[Дерево_отрезков._Построение | дерева отрезков]] следующим образом: построим дерево отрезков для суммы на массиве из единиц. Единица в позиции означает, что данная позиция свободна. Чтобы найти <tex>k</tex>-ую свободную позицию, нужно спускаться (начиная с корня) в левое поддерево если сумма в нём больше, чем <tex>k</tex>, и в правое дерево иначе. |
Данный алгоритм переписывается в код следующим образом: | Данный алгоритм переписывается в код следующим образом: | ||
− | def recover(inv): | + | <font color=green>// <tex>build_segment_tre</tex> {{---}} строит дерево отрезков над массивом</font> |
− | n = | + | <font color=green>// <tex>node</tex> {{---}} вершина дерева</font> |
− | tree = build_segment_tree(array(n, 1)) | + | <font color=green>// <tex>node.index</tex> {{---}} индекс соответствующего элемента в массиве для листа дерева</font> |
− | result = array(n) | + | '''def''' <tex>\mathtt{recover(inv)}:</tex> |
− | curr = 1 | + | <tex>n\ = \mathtt{inv.length}</tex> |
− | for k in inv: | + | <tex>\mathtt{tree}\ = \mathtt{build_segment_tree}(\mathtt{array}(n, 1))</tex> |
− | node = tree.root | + | <tex>\mathtt{result}\ = \mathtt{array}(n)</tex> |
− | while (!node. | + | <tex>curr\ = 1</tex> |
− | if (k < node.left.value): | + | '''for''' <tex>k \in \mathtt{inv}:</tex> |
− | node = node.left | + | <tex>\mathtt{node}\ = \mathtt{tree.root}</tex> |
− | else: | + | '''while''' <tex>(!\mathtt{node.is\_leaf}):</tex> |
− | k -= node.left.value | + | '''if''' <tex>(k <\mathtt{node.left.value}):</tex> |
− | node = node.right | + | <tex>\mathtt{node}\ = \mathtt{node.left}</tex> |
− | result[node.index] = curr | + | '''else:''' |
− | node.add(-1) | + | <tex>k\ -= \mathtt{node.left.value}</tex> |
− | curr + | + | <tex>\mathtt{node}\ = \mathtt{node.right}</tex> |
− | return result | + | <tex>\mathtt{result}[\mathtt{node.index}]\ = curr</tex> |
+ | <tex>\mathtt{node.add}(-1)</tex> | ||
+ | <tex>curr++</tex> | ||
+ | '''return''' <tex>\mathtt{result}</tex> | ||
− | |||
− | |||
− | |||
Этот алгоритм имеет сложность <tex>O(n \log n)</tex>: делается <tex>n</tex> итераций цикла, в которой происходит спуск по дереву высоты <tex>O(\log n)</tex> и один запрос на дереве отрезков. Таким образом, время работы алгоритма на каждой итерации есть <tex>O(\log n)</tex>. | Этот алгоритм имеет сложность <tex>O(n \log n)</tex>: делается <tex>n</tex> итераций цикла, в которой происходит спуск по дереву высоты <tex>O(\log n)</tex> и один запрос на дереве отрезков. Таким образом, время работы алгоритма на каждой итерации есть <tex>O(\log n)</tex>. | ||
Строка 114: | Строка 114: | ||
Рассмотрим пример построения таблицы инверсий и восстановления перестановки по таблице инверсий. Пусть дана перестановка <tex>(5, 7, 1, 3, 4, 6, 8, 2)</tex>. Следующая таблица показывает работу алгоритма за <tex>O(n \log n)</tex>, на каждой строке один уровень рекурсии (на первой строке — самый глубокий). В скобках стоят пары: элемент перестановки, количество инверсий. Полужирным отмечены элементы, у которых обновилось значение количества инверсий на данном шаге. | Рассмотрим пример построения таблицы инверсий и восстановления перестановки по таблице инверсий. Пусть дана перестановка <tex>(5, 7, 1, 3, 4, 6, 8, 2)</tex>. Следующая таблица показывает работу алгоритма за <tex>O(n \log n)</tex>, на каждой строке один уровень рекурсии (на первой строке — самый глубокий). В скобках стоят пары: элемент перестановки, количество инверсий. Полужирным отмечены элементы, у которых обновилось значение количества инверсий на данном шаге. | ||
− | {| style = "border: 0px solid; background-color: gray; text-align: center; padding : 0" cellspacing = " | + | {| 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" |(5, 0) | ||
| style = "background-color: white; padding: 3px 6px" |(7, 0) | | style = "background-color: white; padding: 3px 6px" |(7, 0) | ||
Строка 137: | Строка 137: | ||
Полученная таблица инверсий: <tex>(2, 6, 2, 2, 0, 1, 0, 0)</tex>. Восстановим перестановку по таблице инверсий, начиная с пустого массива. | Полученная таблица инверсий: <tex>(2, 6, 2, 2, 0, 1, 0, 0)</tex>. Восстановим перестановку по таблице инверсий, начиная с пустого массива. | ||
− | {| | + | {| 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</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" 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</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{5}</tex> | ||
|- | |- | ||
− | | 5 || | + | |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>\bf{6}</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{6}</tex> | ||
|- | |- | ||
− | | 5 || | + | |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</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> | ||
|- | |- | ||
− | | 5 || 7 || 1 || 3 || 4 || 6 || | + | |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> | ||
|} | |} | ||
− | == Источники == | + | == Источники информации == |
+ | * [https://en.wikipedia.org/wiki/Permutation Wikipedia {{---}} Permutation] | ||
* Д. Кнут - Искусство программирования, том 3. | * Д. Кнут - Искусство программирования, том 3. | ||
+ | |||
+ | == См. также == | ||
+ | * [[Матричное представление перестановок]] | ||
+ | |||
[[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
[[Категория: Комбинаторика ]] | [[Категория: Комбинаторика ]] |
Версия 18:48, 12 января 2015
Пусть перестановкой чисел .
является
Определение: |
Инверсией (англ. inversion) в перестановке | называется всякая пара индексов такая, что и .
Определение: |
Таблицей инверсий (англ. inversion table) перестановки | называют такую последовательность , в которой равно числу элементов перестановки , стоящих в левее числа и больших .
Содержание
Алгоритм построения за O(N2)
Таблицу инверсий тривиально построить по определению. Для каждого элемента перестановки считаем количество элементов, больших данного и стоящих в перестановке левее него. Алгоритм построения в псевдокоде выглядит так:
T[1..n] = 0 For i = 1..n For j = 1..(i - 1) if P[j] > P[i] T[P[i]] = T[P[i]] + 1
Сложность данного алгоритма — сортировку слиянием.
. Уменьшить время работы можно используя алгоритм, похожий наАлгоритм построения за O(N log N)
Пусть дано разбиение перестановки на два списка, причём для каждого элемента дано число инверсий слева с элементами того же списка и известно, что все числа первого списка стоят левее всех чисел второго списка в исходной перестановке. Будем считать количество инверсий слева элементов обоих списков следующим образом: сливаем списки, аналогично сортировке слиянием.
Если в результат нужно записать элемент первого списка, то все нерассмотренные элементы второго списка больше, следовательно, количество инверсий для этого элемента не меняется. Если в результат нужно записать элемент второго списка, то все нерассмотренные элементы первого списка больше его и стоят левее. Следовательно, количество инверсий для этого элемента следует увеличить на количество нерассмотренных элементов второго списка.
Описанный алгоритм записывается в псевдокод следующим образом:
//— процедура, сливающая два списка пар // — процедура, рекурсивно получающая таблицу инверсий для перестановки def null while and if else: while while return def if return else: return
Сложность представленного алгоритма есть . Алгоритм с такой же сложностью можно построить с помощью дерева отрезков.
Алгоритм восстановления
Для восстановления перестановки по таблицы инверсий
воспользуемся следующим соображением: единица стоит в перестановке на -ом месте (индексируем элементы с нуля), так как остальные числа в перестановке больше единицы. Далее, если известны расположения всех чисел , то число стоит на -ой ещё не занятой позиции: все числа, меньшие уже расставлены. Это рассуждение напрямую переписывается в код следующим образом://— счётчик пропущенных свободных позиций // — количество инверсий слева для элемента curr // — массив, в который записывается перестановка. Равенство элемента массива нулю обозначает, что эта позиция свободна. def for for if if break else: return
Этот простой алгоритм имеет сложность — внутренний цикл делает до итераций, внешний — ровно итераций.
Видно, что для восстановления нужно узнавать дерева отрезков следующим образом: построим дерево отрезков для суммы на массиве из единиц. Единица в позиции означает, что данная позиция свободна. Чтобы найти -ую свободную позицию, нужно спускаться (начиная с корня) в левое поддерево если сумма в нём больше, чем , и в правое дерево иначе.
-ую свободную позицию. Это можно делать с помощьюДанный алгоритм переписывается в код следующим образом:
//— строит дерево отрезков над массивом // — вершина дерева // — индекс соответствующего элемента в массиве для листа дерева def for while if else: return
Этот алгоритм имеет сложность : делается итераций цикла, в которой происходит спуск по дереву высоты и один запрос на дереве отрезков. Таким образом, время работы алгоритма на каждой итерации есть .
Пример
Рассмотрим пример построения таблицы инверсий и восстановления перестановки по таблице инверсий. Пусть дана перестановка
. Следующая таблица показывает работу алгоритма за , на каждой строке один уровень рекурсии (на первой строке — самый глубокий). В скобках стоят пары: элемент перестановки, количество инверсий. Полужирным отмечены элементы, у которых обновилось значение количества инверсий на данном шаге.(5, 0) | (7, 0) | (1, 0) | (3, 0) | (4, 0) | (6, 0) | (8, 0) | (2, 0) |
(5, 0), (7, 0) | (1, 0), (3, 0) | (4, 0), (6, 0) | (2, 1), (8, 0) | ||||
(1, 2), (3, 2), (5, 0), (7, 0) | (2, 3), (4, 0), (6, 0), (8, 0) | ||||||
(1, 2), (2, 6), (3, 2), (4, 2), (5, 0), (6, 1), (7, 0), (8, 0) |
Полученная таблица инверсий:
. Восстановим перестановку по таблице инверсий, начиная с пустого массива.пропускаем две свободных позиции и ставим | ||||||||
пропускаем шесть свободных позиций и ставим | ||||||||
пропускаем две свободных позиции и ставим | ||||||||
пропускаем две свободных позиции и ставим | ||||||||
не пропускаем свободных позиции и ставим | ||||||||
пропускаем одну свободную позицию и ставим | ||||||||
не пропускаем свободных позиций и ставим | ||||||||
не пропускаем свободных позиций и ставим |
Источники информации
- Wikipedia — Permutation
- Д. Кнут - Искусство программирования, том 3.