Таблица инверсий — различия между версиями
м (убран wikitex) |
(→Алгоритм построения) |
||
| Строка 20: | Строка 20: | ||
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>. Уменьшить время работы можно используя алгоритм, похожий на сортировку слиянием. |
| − | + | Пусть дано разбиение перестановки на два списка, причём для каждого элемента дано число инверсий слева с элементами того же списка и известно, что все числа первого списка стоят левее всех чисел второго списка в исходной перестановке. Будем считать количество инверсий слева элементов обоих списков следующим образом: сливаем списки, аналогично сортировке слиянием. | |
| − | |||
| − | + | Если в результат нужно записать элемент первого списка, то все нерассмотренные элементы второго списка больше, следовательно, количество инверсий для этого элемента не меняется. Если в результат нужно записать элемент второго списка, то все нерассмотренные элементы первого списка больше его и стоят левее. Следовательно, количество инверсий для этого элемента следует увеличить на количетво нерассмотренных элементов второго списка. | |
| − | + | Описанный алгоритм записывается в псевдокод следующим образом: | |
| − | + | def inverses_merge(ls1, ls2): | |
| + | result = [] | ||
| + | it1, it2 = 0, 0 | ||
| + | while (it1 < len(ls1)) and (it2 < len(ls2)): | ||
| + | if ls1[it1].item < ls2[it2].item: | ||
| + | result.append(ls1[it1]) | ||
| + | it1 += 1 | ||
| + | else: | ||
| + | result.append(item = ls2[it2].item, inverses = ls2[it2].inverses + len(ls1) - it1) | ||
| + | it2 += 1 | ||
| + | while (it1 < len(ls1)): | ||
| + | result.append(ls1[it1]) | ||
| + | it1 += 1 | ||
| + | while (it2 < len(ls2)): | ||
| + | result.append(ls2[it2]) | ||
| + | it2 += 1 | ||
| + | return result | ||
| + | |||
| + | def inverses_get(ls): | ||
| + | if len(ls) == 1: | ||
| + | return [(item = ls[0], inverses = 0)] | ||
| + | else: | ||
| + | return inverses_merge(inverses_get(ls.first_half), inverses_get(ls.second_half)) | ||
| + | |||
| + | * inverses_merge — процедура, сливающая два списка пар | ||
| + | * inverses_get — процедура, рекурсивно получающая таблицу инверсий для перестановки | ||
| + | |||
| + | Сложность представленного алгоритма есть <tex>O(n\log_2 n)</tex>. Алгоритм с такой же сложностью можно построить с помощью дерева отрезков. | ||
= Алгоритм восстановления = | = Алгоритм восстановления = | ||
Версия 00:05, 13 января 2012
Пусть является перестановкой чисел .
| Определение: |
| Инверсией в перестановке называется всякая пара индексов такая, что и . |
| Определение: |
| Таблицей инверсий перестановки называют такую последовательность , в которой равно числу элементов перестановки , стоящих в левее числа и больших . |
Алгоритм построения
Таблицу инверсий тривиально построить по определению. Для каждого элемента перестановки считаем количество элементов, больших данного и стоящих в перестановке левее него. Алгоритм построения в псевдокоде выглядит так:
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
Сложность данного алгоритма — . Уменьшить время работы можно используя алгоритм, похожий на сортировку слиянием.
Пусть дано разбиение перестановки на два списка, причём для каждого элемента дано число инверсий слева с элементами того же списка и известно, что все числа первого списка стоят левее всех чисел второго списка в исходной перестановке. Будем считать количество инверсий слева элементов обоих списков следующим образом: сливаем списки, аналогично сортировке слиянием.
Если в результат нужно записать элемент первого списка, то все нерассмотренные элементы второго списка больше, следовательно, количество инверсий для этого элемента не меняется. Если в результат нужно записать элемент второго списка, то все нерассмотренные элементы первого списка больше его и стоят левее. Следовательно, количество инверсий для этого элемента следует увеличить на количетво нерассмотренных элементов второго списка.
Описанный алгоритм записывается в псевдокод следующим образом:
def inverses_merge(ls1, ls2):
result = []
it1, it2 = 0, 0
while (it1 < len(ls1)) and (it2 < len(ls2)):
if ls1[it1].item < ls2[it2].item:
result.append(ls1[it1])
it1 += 1
else:
result.append(item = ls2[it2].item, inverses = ls2[it2].inverses + len(ls1) - it1)
it2 += 1
while (it1 < len(ls1)):
result.append(ls1[it1])
it1 += 1
while (it2 < len(ls2)):
result.append(ls2[it2])
it2 += 1
return result
def inverses_get(ls):
if len(ls) == 1:
return [(item = ls[0], inverses = 0)]
else:
return inverses_merge(inverses_get(ls.first_half), inverses_get(ls.second_half))
- inverses_merge — процедура, сливающая два списка пар
- inverses_get — процедура, рекурсивно получающая таблицу инверсий для перестановки
Сложность представленного алгоритма есть . Алгоритм с такой же сложностью можно построить с помощью дерева отрезков.
Алгоритм восстановления
Для восстановления таблицы перестановки из таблицы инверсий создаем таблицу, которую будем расширять, по мере добавления в неё чисел. Добавляем в эту таблицу число (где от до 1) на позицию , где - число в таблице инверсий на -том месте. Данный алгоритм довольно прост в реализации, но без использования дополнительных структур данных, имеет сложность , т. к. для вставки элемента в определённую позицию, требуется порядка перестановок элементов.
Приведём алгоритм восстановления с использованием сортировки слиянием, имеющий сложность .
Пусть и - цепочки упорядоченных пар целых неотрицательных чисел . Рассмотрим двоичную операцию , рекурсивно определенную на парах таких цепочек следующим образом:
$([m, n]\alpha)\circ([m', n']\beta)=\left\{\begin{aligned}[]
[m,n](\alpha \circ ([m'-m, n']\beta)), m \le m',\\
[m', n'](([m-m'-1, n]\alpha) \circ \beta), m>m'.\\
\end{aligned}
\right.$
Сопоставим каждому элементу таблицы инверсий его номер. Получится множество упорядоченных пар чисел , где — сам элемент, а — его номер. Разобьём данные элементы на пары и произведём с ними операцию . Получим некоторое количество цепочек упорядоченных пар. Также разбиваем их на пары и производим операцию . Так действуем, пока не останется одна цепочка. Выписывая вторые элементы данных упорядоченных пар в том порядке, в каком они представлены в цепочке, получим первоначальную перестановку.
Цепочка наподобие представляет "_ _ _ _ 4 _ 3 _ ", где "_" означает пропуск. Операция вставляет пропуски и заполнения из на место пропусков в .
Пример
- - таблица инверсий.
Получаем перестановку
Источники
- Д. Кнут - Искусство программирования, том 3.