Таблица инверсий — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м (Алгоритм построения)
Строка 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

Пусть [math] P = (p_1,p_2,\dots,p_n)[/math] является перестановкой чисел [math] 1, 2,\dots, n[/math].


Определение:
Инверсией в перестановке [math]P[/math] называется всякая пара индексов [math]i, j[/math] такая, что [math]1\leqslant i\lt j\leqslant n[/math] и [math]P[i]\gt P[j][/math].


Определение:
Таблицей инверсий перестановки [math] P [/math] называют такую последовательность [math] T = (t_1,t_2,\dots,t_n)[/math], в которой [math]t_i[/math] равно числу элементов перестановки [math] P [/math], стоящих в [math] P [/math] левее числа [math]i[/math] и больших [math]i[/math].


Алгоритм построения

Таблицу инверсий тривиально построить по определению. Для каждого элемента перестановки считаем количество элементов, больших данного и стоящих в перестановке левее него. Алгоритм построения в псевдокоде выглядит так:

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

Сложность данного алгоритма — [math]O(n^2)[/math]. Уменьшить время работы можно используя алгоритм, похожий на сортировку слиянием.

Пусть дано разбиение перестановки на два списка, причём для каждого элемента дано число инверсий слева с элементами того же списка и известно, что все числа первого списка стоят левее всех чисел второго списка в исходной перестановке. Будем считать количество инверсий слева элементов обоих списков следующим образом: сливаем списки, аналогично сортировке слиянием.

Если в результат нужно записать элемент первого списка, то все нерассмотренные элементы второго списка больше, следовательно, количество инверсий для этого элемента не меняется. Если в результат нужно записать элемент второго списка, то все нерассмотренные элементы первого списка больше его и стоят левее. Следовательно, количество инверсий для этого элемента следует увеличить на количетво нерассмотренных элементов второго списка.

Описанный алгоритм записывается в псевдокод следующим образом:

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 — процедура, рекурсивно получающая таблицу инверсий для перестановки

Сложность представленного алгоритма есть [math]O(n\log n)[/math]. Алгоритм с такой же сложностью можно построить с помощью дерева отрезков.

Алгоритм восстановления

Для восстановления перестановки по таблицы инверсий [math]T[/math] воспользуемся следующим соображением: единица стоит на [math]T_i[/math]-ом месте (индексируем элементы с нуля), так как остальные числа в перестановке больше единицы. Далее, если известны расположения всех чисел [math]1, \dots, k[/math], то число [math]k + 1[/math] стоит на [math]T_{k + 1}[/math]-ой ещё не занятой позиции: все числа, меньшие [math]k + 1[/math] уже расставлены. Это рассуждение напрямую переписывается в код следующим образом:

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 — массив, в который записывается перестановка. Равенство элемента массива нулю обозначает, что эта позиция свободна.

Этот простой алгоритм имеет сложность [math]O(n^2)[/math] — внутренний цикл делает до [math]n[/math] итераций, внешний — ровно [math]n[/math] итераций.

Видно, что для восстановления нужно узнавать [math]k[/math]-ую свободную позицию. Это можно делать с помощью дерева отрезков следующим образом: построим дерево отрезков для суммы на массиве из единиц. Единица в позиции означает, что данная позиция свободна. Чтобы найти [math]k[/math]-ую свободную позицию, нужно спускаться (начиная с корня) в левое поддерево если сумма в нём больше, чем [math]k[/math], и в правое дерево иначе.

Данный алгоритм переписывается в код следующим образом:

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 — индекс соответсвующего элемента в массиве для листа дерева

Этот алгоритм имеет сложность [math]O(n \log n)[/math]: делается [math]n[/math] итераций цикла, в которой происходит спуск по дереву высоты [math]O(\log n)[/math] и один запрос на дереве отрезков. Таким образом, время работы алгоритма на каждой итерации есть [math]O(\log n)[/math].

Пример

Рассмотрим пример построения таблицы инверсий и восстановления перестановки по таблице инверсий. Пусть дана перестановка [math](5, 7, 1, 3, 4, 6, 8, 2)[/math].

(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)

Полученная таблица инверсий: [math](2, 6, 2, 2, 0, 1, 0, 0)[/math]. Восстановим перестановку по таблице инверсий

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.