Таблица инверсий — различия между версиями
м (→Алгоритм построения) |
|||
| Строка 5: | Строка 5: | ||
'''Инверсией''' в перестановке <tex>P</tex> называется всякая пара индексов <tex>i, j</tex> такая, что <tex>1\leqslant i<j\leqslant n</tex> и <tex>P[i]>P[j]</tex>. | '''Инверсией''' в перестановке <tex>P</tex> называется всякая пара индексов <tex>i, j</tex> такая, что <tex>1\leqslant i<j\leqslant n</tex> и <tex>P[i]>P[j]</tex>. | ||
}} | }} | ||
| − | |||
{{Определение | {{Определение | ||
|definition = | |definition = | ||
| Строка 11: | Строка 10: | ||
}} | }} | ||
| − | = Алгоритм построения = | + | == Алгоритм построения == |
Таблицу инверсий тривиально построить по определению. Для каждого элемента перестановки считаем количество элементов, больших данного и стоящих в перестановке левее него. | Таблицу инверсий тривиально построить по определению. Для каждого элемента перестановки считаем количество элементов, больших данного и стоящих в перестановке левее него. | ||
| Строка 57: | Строка 56: | ||
Сложность представленного алгоритма есть <tex>O(n\log n)</tex>. Алгоритм с такой же сложностью можно построить с помощью дерева отрезков. | Сложность представленного алгоритма есть <tex>O(n\log n)</tex>. Алгоритм с такой же сложностью можно построить с помощью дерева отрезков. | ||
| − | = Алгоритм восстановления = | + | == Алгоритм восстановления == |
Для восстановления перестановки по таблицы инверсий <tex>T</tex> воспользуемся следующим соображением: единица стоит на <tex>T_i</tex>-ом месте (индексируем элементы с нуля), так как остальные числа в перестановке больше единицы. Далее, если известны расположения всех чисел <tex>1, \dots, k</tex>, то число <tex>k + 1</tex> стоит на <tex>T_{k + 1}</tex>-ой ещё не занятой позиции: все числа, меньшие <tex>k + 1</tex> уже расставлены. Это рассуждение напрямую переписывается в код следующим образом: | Для восстановления перестановки по таблицы инверсий <tex>T</tex> воспользуемся следующим соображением: единица стоит на <tex>T_i</tex>-ом месте (индексируем элементы с нуля), так как остальные числа в перестановке больше единицы. Далее, если известны расположения всех чисел <tex>1, \dots, k</tex>, то число <tex>k + 1</tex> стоит на <tex>T_{k + 1}</tex>-ой ещё не занятой позиции: все числа, меньшие <tex>k + 1</tex> уже расставлены. Это рассуждение напрямую переписывается в код следующим образом: | ||
| Строка 111: | Строка 110: | ||
Этот алгоритм имеет сложность <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>. | ||
| − | = Источники = | + | == Пример == |
| + | |||
| + | Рассмотрим пример построения таблицы инверсий и восстановления перестановки по таблице инверсий. Пусть дана перестановка <tex>(5, 7, 1, 3, 4, 6, 8, 2)</tex>. | ||
| + | |||
| + | {| style = "border-style: solid; border-color: gray; text-align: center" cellpadding = "2" border = "1" | ||
| + | | | ||
| + | (5, 0) | ||
| + | | (7, 0) | ||
| + | | (1, 0) | ||
| + | | (3, 0) | ||
| + | | (4, 0) | ||
| + | | (6, 0) | ||
| + | | (8, 0) | ||
| + | | (2, 0) | ||
| + | |- | ||
| + | |colspan = "2"| | ||
| + | (5, 0), (7, 0) | ||
| + | |colspan = "2"| | ||
| + | (1, 0), (3, 0) | ||
| + | |colspan = "2"| | ||
| + | (4, 0), (6, 0) | ||
| + | |colspan = "2"| | ||
| + | (2, 1), (8, 0) | ||
| + | |- | ||
| + | |colspan = "4"| | ||
| + | (1, 2), (3, 2), (5, 0), (7, 0) | ||
| + | |colspan = "4"| | ||
| + | (2, 3), (4, 0), (6, 0), (8, 0) | ||
| + | |- | ||
| + | |colspan = "8"| | ||
| + | (1, 2), (2, 6), (3, 2), (4, 2), (5, 0), (6, 1), (7, 0), (8, 0) | ||
| + | |} | ||
| + | |||
| + | Полученная таблица инверсий: <tex>(2, 6, 2, 2, 0, 1, 0, 0)</tex>. Восстановим перестановку по таблице инверсий | ||
| + | |||
| + | {| border = 1 cellpadding = "3" | ||
| + | | || || '''1''' || || || || || || пропускаем две свободных позиции и ставим 1 | ||
| + | |- | ||
| + | | || || 1 || || || || '''2''' || || пропускаем шесть свободных позиций и ставим 2 | ||
| + | |- | ||
| + | | || || 1 || '''3''' || || || 2 || || пропускаем две свободных позиции и ставим 3 | ||
| + | |- | ||
| + | | || || 1 || 3 || '''4''' || || 2 || || пропускаем две свободных позиции и ставим 4 | ||
| + | |- | ||
| + | | '''5''' || || 1 || 3 || 4 || || 2 || || не пропускаем свободных позиции и ставим 5 | ||
| + | |- | ||
| + | | 5 || || 1 || 3 || 4 || '''6''' || 2 || || пропускаем одну свободную позицию и ставим 6 | ||
| + | |- | ||
| + | | 5 || '''7''' || 1 || 3 || 4 || 6 || 2 || || не пропускаем свободных позиций и ставим 7 | ||
| + | |- | ||
| + | | 5 || 7 || 1 || 3 || 4 || 6 || 2 || '''8''' || не пропускаем свободных позиций и ставим 8 | ||
| + | |} | ||
| + | |||
| + | == Источники == | ||
* Д. Кнут - Искусство программирования, том 3. | * Д. Кнут - Искусство программирования, том 3. | ||
Версия 14:44, 16 января 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 — процедура, рекурсивно получающая таблицу инверсий для перестановки
Сложность представленного алгоритма есть . Алгоритм с такой же сложностью можно построить с помощью дерева отрезков.
Алгоритм восстановления
Для восстановления перестановки по таблицы инверсий воспользуемся следующим соображением: единица стоит на -ом месте (индексируем элементы с нуля), так как остальные числа в перестановке больше единицы. Далее, если известны расположения всех чисел , то число стоит на -ой ещё не занятой позиции: все числа, меньшие уже расставлены. Это рассуждение напрямую переписывается в код следующим образом:
def recover_straight(ls):
n = len(ls)
result = array(0, n)
curr = 1
for k in ls:
j = 0
for (i = 0, i < n, i += 1):
if result[i] == 0:
if j == k:
result[i] = curr
break
else:
j += 1
curr += 1
return result
- j — счётчик пропущенных свободных позиций
- k — количество инверсий слева для элемента curr
- result — массив, в который записывается перестановка. Равенство элемента массива нулю обозначает, что эта позиция свободна.
Этот простой алгоритм имеет сложность — внутренний цикл делает до итераций, внешний — ровно итераций.
Видно, что для восстановления нужно узнавать -ую свободную позицию. Это можно делать с помощью дерева отрезков следующим образом: построим дерево отрезков для суммы на массиве из единиц. Единица в позиции означает, что данная позиция свободна. Чтобы найти -ую свободную позицию, нужно спускаться (начиная с корня) в левое поддерево если сумма в нём больше, чем , и в правое дерево иначе.
Данный алгоритм переписывается в код следующим образом:
def recover(inv):
n = len(inv)
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.value):
node = node.left
else:
k -= node.left.value
node = node.right
result[node.index] = curr
node.add(-1)
curr += 1
return result
- build_segment_tree — строит дерево отрезков над массивом
- node — вершина дерева
- node.index — индекс соответсвующего элемента в массиве для листа дерева
Этот алгоритм имеет сложность : делается итераций цикла, в которой происходит спуск по дереву высоты и один запрос на дереве отрезков. Таким образом, время работы алгоритма на каждой итерации есть .
Пример
Рассмотрим пример построения таблицы инверсий и восстановления перестановки по таблице инверсий. Пусть дана перестановка .
|
(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) | |||||||
Полученная таблица инверсий: . Восстановим перестановку по таблице инверсий
| 1 | пропускаем две свободных позиции и ставим 1 | |||||||
| 1 | 2 | пропускаем шесть свободных позиций и ставим 2 | ||||||
| 1 | 3 | 2 | пропускаем две свободных позиции и ставим 3 | |||||
| 1 | 3 | 4 | 2 | пропускаем две свободных позиции и ставим 4 | ||||
| 5 | 1 | 3 | 4 | 2 | не пропускаем свободных позиции и ставим 5 | |||
| 5 | 1 | 3 | 4 | 6 | 2 | пропускаем одну свободную позицию и ставим 6 | ||
| 5 | 7 | 1 | 3 | 4 | 6 | 2 | не пропускаем свободных позиций и ставим 7 | |
| 5 | 7 | 1 | 3 | 4 | 6 | 2 | 8 | не пропускаем свободных позиций и ставим 8 |
Источники
- Д. Кнут - Искусство программирования, том 3.