Таблица инверсий — различия между версиями
6yry6e (обсуждение | вклад) |
6yry6e (обсуждение | вклад) |
||
Строка 17: | Строка 17: | ||
'''for''' i = 1..n | '''for''' i = 1..n | ||
'''for''' j = 1..(i - 1) | '''for''' j = 1..(i - 1) | ||
− | '''if''' P | + | '''if''' P[j] > P[i] |
T[P[i]] = T[P[i]]++ | T[P[i]] = T[P[i]]++ | ||
Сложность данного алгоритма {{---}} <tex>O(n^2)</tex>. Уменьшить время работы можно используя алгоритм, похожий на [[Сортировка_слиянием |сортировку слиянием.]] | Сложность данного алгоритма {{---}} <tex>O(n^2)</tex>. Уменьшить время работы можно используя алгоритм, похожий на [[Сортировка_слиянием |сортировку слиянием.]] | ||
Строка 30: | Строка 30: | ||
<font color=green>// ''inverses_merge'' {{---}} процедура, сливающая два списка пар</font> | <font color=green>// ''inverses_merge'' {{---}} процедура, сливающая два списка пар</font> | ||
<font color=green>// ''inverses_get'' {{---}} процедура, рекурсивно получающая таблицу инверсий для перестановки</font> | <font color=green>// ''inverses_get'' {{---}} процедура, рекурсивно получающая таблицу инверсий для перестановки</font> | ||
− | '''def''' inverses_merge(ls1, ls2) | + | '''def''' inverses_merge(ls1, ls2): |
result = [] | result = [] | ||
it1, it2 = ''null'' | it1, it2 = ''null'' | ||
'''while''' (it1 < ls1.length) '''and''' (it2 < ls2.length): | '''while''' (it1 < ls1.length) '''and''' (it2 < ls2.length): | ||
− | '''if''' ls1[it1 | + | '''if''' ls1[it1].item < ls2[it2].item |
result.append(ls1[it1]) | result.append(ls1[it1]) | ||
it1++ | it1++ | ||
− | '''else | + | '''else''' |
result.append(item = ls2[it2].item, inverses = ls2[it2].inverses + ls1.length - it1) | result.append(item = ls2[it2].item, inverses = ls2[it2].inverses + ls1.length - it1) | ||
it2++ | it2++ | ||
− | '''while''' it1 < ls1.length | + | '''while''' it1 < ls1.length |
result.append(ls1[it1]) | result.append(ls1[it1]) | ||
it1++ | it1++ | ||
− | '''while''' | + | '''while''' it2 < ls2.length |
result.append(ls2[it2]) | result.append(ls2[it2]) | ||
it2++ | it2++ | ||
'''return''' result | '''return''' result | ||
− | '''def''' inverses_get(ls) | + | '''def''' inverses_get(ls): |
'''if''' ls.length == 1 | '''if''' ls.length == 1 | ||
'''return''' [(item = ls[0], inverses = 0)] | '''return''' [(item = ls[0], inverses = 0)] | ||
− | '''else | + | '''else''' |
'''return''' inverses_merge(inverses_get(ls.first_half), inverses_get(ls.second_half)) | '''return''' inverses_merge(inverses_get(ls.first_half), inverses_get(ls.second_half)) | ||
Строка 67: | Строка 67: | ||
result = array(0, n) | result = array(0, n) | ||
curr = 1 | curr = 1 | ||
− | '''for''' k ''in'' ls | + | '''for''' k '''in''' ls |
j = 0 | j = 0 | ||
− | '''for''' i..n | + | '''for''' i = 0..(n - 1) |
'''if''' result[i] == 0 | '''if''' result[i] == 0 | ||
'''if''' j == k | '''if''' j == k | ||
Строка 94: | Строка 94: | ||
result = array(n) | result = array(n) | ||
curr = 1 | curr = 1 | ||
− | '''for''' k ''in'' inv | + | '''for''' k '''in''' inv |
node = tree.root | node = tree.root | ||
'''while''' !node.is_leaf | '''while''' !node.is_leaf | ||
'''if''' k < node.left.value | '''if''' k < node.left.value | ||
node = node.left | node = node.left | ||
− | '''else | + | '''else''' |
k -= node.left.value | k -= node.left.value | ||
node = node.right | node = node.right |
Версия 16:24, 13 января 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]]++
Сложность данного алгоритма — сортировку слиянием.
. Уменьшить время работы можно используя алгоритм, похожий наАлгоритм построения за O(N log N)
Пусть дано разбиение перестановки на два списка, причём для каждого элемента дано число инверсий слева с элементами того же списка и известно, что все числа первого списка стоят левее всех чисел второго списка в исходной перестановке. Будем считать количество инверсий слева элементов обоих списков следующим образом: сливаем списки, аналогично сортировке слиянием.
Если в результат нужно записать элемент первого списка, то все нерассмотренные элементы второго списка больше, следовательно, количество инверсий для этого элемента не меняется. Если в результат нужно записать элемент второго списка, то все нерассмотренные элементы первого списка больше его и стоят левее. Следовательно, количество инверсий для этого элемента следует увеличить на количество нерассмотренных элементов второго списка.
Описанный алгоритм записывается в псевдокод следующим образом:
// inverses_merge — процедура, сливающая два списка пар // inverses_get — процедура, рекурсивно получающая таблицу инверсий для перестановки def inverses_merge(ls1, ls2): result = [] it1, it2 = null while (it1 < ls1.length) and (it2 < ls2.length): if ls1[it1].item < ls2[it2].item result.append(ls1[it1]) it1++ else result.append(item = ls2[it2].item, inverses = ls2[it2].inverses + ls1.length - it1) it2++ while it1 < ls1.length result.append(ls1[it1]) it1++ while it2 < ls2.length result.append(ls2[it2]) it2++ return result def inverses_get(ls): if ls.length == 1 return [(item = ls[0], inverses = 0)] else return inverses_merge(inverses_get(ls.first_half), inverses_get(ls.second_half))
Сложность представленного алгоритма есть . Алгоритм с такой же сложностью можно построить с помощью дерева отрезков.
Алгоритм восстановления
Для восстановления перестановки по таблицы инверсий
воспользуемся следующим соображением: единица стоит в перестановке на -ом месте (индексируем элементы с нуля), так как остальные числа в перестановке больше единицы. Далее, если известны расположения всех чисел , то число стоит на -ой ещё не занятой позиции: все числа, меньшие уже расставлены. Это рассуждение напрямую переписывается в код следующим образом:// j — счётчик пропущенных свободных позиций // k — количество инверсий слева для элемента curr // result — массив, в который записывается перестановка. Равенство элемента массива нулю обозначает, что эта позиция свободна. 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
Этот простой алгоритм имеет сложность — внутренний цикл делает до итераций, внешний — ровно итераций.
Видно, что для восстановления нужно узнавать дерева отрезков следующим образом: построим дерево отрезков для суммы на массиве из единиц. Единица в позиции означает, что данная позиция свободна. Чтобы найти -ую свободную позицию, нужно спускаться (начиная с корня) в левое поддерево если сумма в нём больше, чем , и в правое дерево иначе.
-ую свободную позицию. Это можно делать с помощьюДанный алгоритм переписывается в код следующим образом:
// build_segment_tree — строит дерево отрезков над массивом // node — вершина дерева // node.index — индекс соответствующего элемента в массиве для листа дерева 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
Этот алгоритм имеет сложность : делается итераций цикла, в которой происходит спуск по дереву высоты и один запрос на дереве отрезков. Таким образом, время работы алгоритма на каждой итерации есть .
Пример
Рассмотрим пример построения таблицы инверсий и восстановления перестановки по таблице инверсий. Пусть дана перестановка
. Следующая таблица показывает работу алгоритма за , на каждой строке один уровень рекурсии (на первой строке — самый глубокий). В скобках стоят пары: элемент перестановки, количество инверсий. Полужирным отмечены элементы, у которых обновилось значение количества инверсий на данном шаге.(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 — 29-31 с.