Підрахунок інверсій у масиві


108

Я розробляю алгоритм, щоб зробити наступне: Дано масив A[1... n], для кожного i < jзнайдіть усі пари інверсії такі A[i] > A[j]. Я використовую сортування об'єднань і копіюю масив A до масиву B, а потім порівнюю два масиви, але мені важко бачити, як я можу використовувати це для пошуку кількості інверсій. Будемо дуже вдячні за будь-які підказки чи допомогу.

Відповіді:


139

Тож ось рішення O (n log n) у Java.

long merge(int[] arr, int[] left, int[] right) {
    int i = 0, j = 0, count = 0;
    while (i < left.length || j < right.length) {
        if (i == left.length) {
            arr[i+j] = right[j];
            j++;
        } else if (j == right.length) {
            arr[i+j] = left[i];
            i++;
        } else if (left[i] <= right[j]) {
            arr[i+j] = left[i];
            i++;                
        } else {
            arr[i+j] = right[j];
            count += left.length-i;
            j++;
        }
    }
    return count;
}

long invCount(int[] arr) {
    if (arr.length < 2)
        return 0;

    int m = (arr.length + 1) / 2;
    int left[] = Arrays.copyOfRange(arr, 0, m);
    int right[] = Arrays.copyOfRange(arr, m, arr.length);

    return invCount(left) + invCount(right) + merge(arr, left, right);
}

Це майже нормальний сорт злиття, вся магія прихована у функції злиття. Зауважте, що алгоритм сортування видаляє інверсії. Під час злиття алгоритму підраховується кількість видалених інверсій (відсортовано, можна сказати).

Єдиний момент, коли інверсії видаляються, це коли алгоритм бере елемент з правого боку масиву і об'єднує його в основний масив. Кількість інверсій, видалених цією операцією, - це кількість елементів, що залишилися від лівого масиву, який потрібно об'єднати. :)

Сподіваюся, це досить пояснювально.


2
Я спробував це запустити, і я не отримав правильної відповіді. Ви повинні зателефонувати invCount (intArray) всередині основного, щоб розпочати роботу? З intArray є несортованим масивом int's? Я запустив його з масивом безлічі цілих чисел і отримав -1887062008 як свою відповідь. Що я роблю неправильно?
Nearpoint

4
+1, див. Подібне рішення в C ++ 11 , включаючи загальний ітераторний розчин та зразок випадкового тесту, використовуючи послідовності з 5-25 елементів. Насолоджуйтесь !.
WhozCraig

3
Це не рішення. Я спробував запустити його, і це дає невірні результати.
mirgee

2
Вибачте за новинчасте запитання, але що з додаванням left.length - iдо лічильника інверсії? Я вважаю, що було б доцільно просто додати 1, оскільки ви потрапили в логічний випадок, коли порівняння між двома підрядами має більший елемент лівого масиву, ніж правий. Хтось може мені це пояснити, як мені 5?
Альфредо Галлегос

2
@AlfredoGallegos, коротка ілюстрація відповіді Марека. Розглянемо два масиви: [6, 8] та [4, 5]. Коли ви бачите, що 6 більше, ніж 4, ви берете 4 і поміщаєте його arr. Але це не одна інверсія. Ви знайшли інверсії для всіх елементів у лівому масиві, які перевищують 6. У нашому випадку він також включає 8. Отже, додається 2 count, що дорівнює left.length - i.
ilya

86

Я знайшов його в O (n * log n) час наступним методом.

  1. Об'єднати масив сортування A та створити копію (масив B)
  2. Візьміть A [1] і знайдіть його положення в відсортованому масиві B за допомогою двійкового пошуку. Кількість інверсій для цього елемента буде на одну меншу, ніж число індексу його положення в B, оскільки кожне нижнє число, яке з’явиться після першого елемента A, буде інверсією.

    2а. накопичити кількість інверсій для протистояння змінної num_inversions.

    2б. видалити A [1] з масиву A, а також з відповідного положення в масиві B

  3. повторіть з кроку 2, поки в A. не буде більше елементів.

Ось приклад виконання цього алгоритму. Оригінальний масив A = (6, 9, 1, 14, 8, 12, 3, 2)

1: Об'єднати сортування та скопіювати у масив B

B = (1, 2, 3, 6, 8, 9, 12, 14)

2: Візьміть A [1] та двійковий пошук, щоб знайти його у масиві B

A [1] = 6

B = (1, 2, 3, 6 , 8, 9, 12, 14)

6 знаходиться в 4-му положенні масиву B, таким чином, є 3 інверсії. Ми знаємо це, тому що 6 був на першому положенні в масиві A, тому будь-який елемент нижчого значення, який згодом з'являється в масиві A, мав би індекс j> i (оскільки i в цьому випадку 1).

2.b: Видаліть A [1] з масиву A, а також з його відповідного положення в масиві B (вилучені жирні елементи).

A = ( 6, 9, 1, 14, 8, 12, 3, 2) = (9, 1, 14, 8, 12, 3, 2)

B = (1, 2, 3, 6, 8, 9, 12, 14) = (1, 2, 3, 8, 9, 12, 14)

3: Перейдіть з кроку 2 на нових масивах A і B.

A [1] = 9

B = (1, 2, 3, 8, 9, 12, 14)

9 зараз знаходиться у 5-му положенні масиву B, таким чином, є 4 інверсії. Ми знаємо це, тому що 9 був на першій позиції в масиві A, тому будь-який елемент нижчого значення, який згодом з'явиться, мав би індекс j> i (оскільки я в цьому випадку знову 1). Видаліть A [1] з масиву A, а також з відповідного положення в масиві B (вилучені жирні елементи)

А = ( 9 , 1, 14, 8, 12, 3, 2) = (1, 14, 8, 12, 3, 2)

B = (1, 2, 3, 8, 9 , 12, 14) = (1, 2, 3, 8, 12, 14)

Продовження в цій галузі дасть нам загальну кількість інверсій для масиву A, як тільки цикл буде завершений.

Крок 1 (сортування об'єднань) потребує виконання O (n * log n) для виконання. Крок 2 виконає n разів, і при кожному виконанні буде виконуватися двійковий пошук, який вимагає виконання O (log n) для загальної кількості O (n * log n). Таким чином, загальний час роботи буде O (n * log n) + O (n * log n) = O (n * log n).

Спасибі за вашу допомогу. Виписування зразків масивів на аркуші паперу справді допомогло візуалізувати проблему.


1
навіщо використовувати сортування злиття не швидке сортування?
Алькотт

5
@Alcott Quick сортування має найгірший час роботи O (n ^ 2), коли список вже відсортований, і перше зведення вибирається кожен раунд. Найгірший випадок злиття - це O (n log n)
користувач482594,

29
Крок видалення зі стандартного масиву робить ваш алгоритм O (n ^ 2), завдяки зміщенню значень. (Ось чому сортування вставки є O (n ^ 2))
Кайл Батт

починаючи з першого елемента масиву B і підраховуючи елементи перед ним у масиві A, також дав би такий же результат, за умови, що ви їх усунете, як ви описали у своїй відповіді.
tutak

@el diablo Як видалити елементи, щоб уникнути складності n ^ 2 ??
Jerky

26

У Python

# O(n log n)

def count_inversion(lst):
    return merge_count_inversion(lst)[1]

def merge_count_inversion(lst):
    if len(lst) <= 1:
        return lst, 0
    middle = int( len(lst) / 2 )
    left, a = merge_count_inversion(lst[:middle])
    right, b = merge_count_inversion(lst[middle:])
    result, c = merge_count_split_inversion(left, right)
    return result, (a + b + c)

def merge_count_split_inversion(left, right):
    result = []
    count = 0
    i, j = 0, 0
    left_len = len(left)
    while i < left_len and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            count += left_len - i
            j += 1
    result += left[i:]
    result += right[j:]
    return result, count        


#test code
input_array_1 = []  #0
input_array_2 = [1] #0
input_array_3 = [1, 5]  #0
input_array_4 = [4, 1] #1
input_array_5 = [4, 1, 2, 3, 9] #3
input_array_6 = [4, 1, 3, 2, 9, 5]  #5
input_array_7 = [4, 1, 3, 2, 9, 1]  #8

print count_inversion(input_array_1)
print count_inversion(input_array_2)
print count_inversion(input_array_3)
print count_inversion(input_array_4)
print count_inversion(input_array_5)
print count_inversion(input_array_6)
print count_inversion(input_array_7)

13
Мене бентежить те, як це вдалося дістатись до +13 - я не особливо досвідчений в Python, але це здається майже таким же, як версія Java, представлена ​​2 роки тому , за винятком того, що це не дає жодного пояснення . Публікація відповідей на будь-якій іншій мові є активно шкідливою для ІМО - можливо, це тисячі, якщо не набагато більше мов - я сподіваюся, що ніхто не буде заперечувати, що нам слід надсилати тисячі відповідей на запитання - обмін стеками не робився для цього .
Бернхард Баркер

1
@tennenrishin Добре, може, не тисячі. Але де ми проведемо лінію? Наразі, як я вважаю, десять відповідей дають той самий підхід . Це приблизно 43% відповідей (без невідповіді) - це зовсім небагато місця, враховуючи, що тут представлено півдесятка інших підходів. Навіть якщо для одного і того ж підходу є лише 2 відповіді, це все одно зайве розбавляє відповіді. І я зробив досить пристойний аргумент, щоб ця відповідь не була корисною в попередньому коментарі.
Бернхард Баркер

3
@Dukeling Як ​​і ви, я незнайомий з Python і більше знайомий з Java. Я вважаю це рішення набагато менш читабельним, ніж Java. Тоді, очевидно, для деяких людей зворотне може бути правдивим в тій же мірі.
Мудрий

3
Для переважної більшості користувачів python близький до коду sudo. Я, чесно, вважаю, що це набагато читабельніше, ніж у Java, навіть не маючи пояснень. Я не бачу необхідності так дратуватися, якщо це допомагає деяким читачам.
Франциско Варгас

2
Це рішення є ідеальним та зрозумілим для користувачів python. Люди хочуть побачити, як інші реалізували це в Python.
Ерін

24

Цікаво, чому ще ніхто не згадував бінарні індексировані дерева . Ви можете використовувати його для підтримки суми префікса значень елементів перестановки. Тоді ви можете просто перейти справа наліво і порахувати для кожного елемента кількість елементів, меншу за нього праворуч:

def count_inversions(a):
  res = 0
  counts = [0]*(len(a)+1)
  rank = { v : i+1 for i, v in enumerate(sorted(a)) }
  for x in reversed(a):
    i = rank[x] - 1
    while i:
      res += counts[i]
      i -= i & -i
    i = rank[x]
    while i <= len(a):
      counts[i] += 1
      i += i & -i
  return res

Складність становить O (n log n), а постійний коефіцієнт дуже низький.


Мабуть, найкращий підхід :)
Nilutpal Borgohain

@NilutpalBorgohain Спасибі :) Здається, що потрібно хоча б найменший код серед кандидатів O (n log n).
Ніклас Б.

1
Дякую за це Яке значення i -= i & -iрядка? І аналогічноi += i & -i
Джерард Кондон

1
@GerardCondon - це в основному структура даних BIT. Посилання, що пояснює це, можна знайти у відповіді
Ніклас Б.

TIL про дерева Fenwick. Дякую! Я опублікував відповідь , яка timeitзіставляє всі відповіді Python на це питання, тому він включає ваш код. Можливо, вам буде цікаво переглянути результати часу.
PM 2Ring

14

Насправді у мене виникло питання, подібне до домашнього завдання. Мене обмежили, що він повинен мати ефективність O (nlogn).

Я використав ідею, яку ви запропонували використовувати Mergesort, оскільки вона вже має правильну ефективність. Я просто вставив якийсь код у функцію злиття, яка була в основному: Щоразу, коли число з масиву праворуч додається до вихідного масиву, я додаю до загальної кількості інверсій, кількість чисел, що залишаються в лівому масиві.

Це має для мене багато сенсу зараз, коли я досить подумав про це. Ваш підрахунок, у скільки разів більша кількість надходить перед будь-якими цифрами.

hth.


6
Я підтримую вашу відповідь, істотна відмінність від сортування злиття полягає у функції злиття, коли елемент 2-го правого масиву копіюється на вихідний масив => лічильник інверсії приросту за кількістю елементів, що залишилися в першому лівому масиві
Alex.Salnikov

11

Основна мета цієї відповіді - порівняння швидкостей різних версій Python, які можна знайти тут, але я також маю кілька власних внесків. (FWIW, я щойно виявив це питання під час пошуку дублікатів).

Відносні швидкості виконання алгоритмів, реалізованих у CPython, можуть бути різними, ніж очікувати від простого аналізу алгоритмів та досвіду з іншими мовами. Це тому, що Python надає багато потужних функцій та методів, реалізованих на C, які можуть працювати зі списками та іншими колекціями на близькій швидкості, яку можна отримати повністю складеною мовою, тому ці операції виконуються набагато швидше, ніж еквівалентні алгоритми, реалізовані "вручну" з Python код.

Код, який використовує ці інструменти, часто може перевершувати теоретично переважні алгоритми, які намагаються зробити все з операціями Python на окремих елементах колекції. Звичайно, на це впливає і фактична кількість даних, що обробляються. Але для помірних обсягів даних код, який використовує алгоритм O (n²), що працює зі швидкістю C, може легко перемогти алгоритм O (n log n), який робить основну частину своєї роботи з окремими операціями Python.

Багато хто з опублікованих відповідей на це запитання підрахунку інверсії використовують алгоритм, заснований на об'єднанні. Теоретично це хороший підхід, якщо тільки розмір масиву не дуже маленький. Але вбудований в Python TimSort (гібридний стабільний алгоритм сортування, отриманий від сортування злиття та сортування вставки) працює зі швидкістю С, і кодований вручну злиття в Python не може сподіватися конкурувати з ним за швидкість.

Одне з найбільш інтригуючих рішень тут, у відповіді, опублікованій Нікласом Б , використовує вбудований сортування для визначення ранжування елементів масиву, а двійкове індексоване дерево (aka дерево Fenwick) для зберігання сукупних сум, необхідних для обчислення інверсії. рахувати. У процесі спроби зрозуміти цю структуру даних та алгоритм Нікласа я написав кілька власних варіантів (розміщені нижче). Але я також виявив, що для помірних розмірів списку фактично швидше використовувати вбудовану sumфункцію Python, ніж прекрасне дерево Fenwick.

def count_inversions(a):
    total = 0
    counts = [0] * len(a)
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        total += sum(counts[:i])
        counts[i] += 1
    return total

Врешті-решт, коли розмір списку налічує близько 500, аспект O (n²) виклику sumвсередині цього forциклу отримує негарну голову, і продуктивність починає скорочуватися.

Mergesort - не єдиний сорт O (nlogn), і кілька інших можуть бути використані для підрахунку інверсії. у відповіді prasadvk використовується двійкове сортування дерева, однак, схоже, його код знаходиться у C ++ або в одній із його похідних. Тому я додав версію Python. Спочатку я використовував клас для реалізації деревних вузлів, але виявив, що диктат помітно швидший. Я врешті-решт використав список, який є ще швидшим, хоча це робить код трохи менш читабельним.

Один із бонусів деревця - це те, що її реалізувати набагато простіше, ніж об'єднати. Python не оптимізує рекурсію і має обмеження глибини рекурсії (хоча це можна збільшити, якщо вам це справді потрібно). І звичайно, виклики функції Python відносно повільні, тому, коли ви намагаєтесь оптимізувати швидкість, добре уникати функціональних викликів, коли це практично.

Ще один сорт O (nlogn) - це поважний радікс-сорт. Велика перевага полягає в тому, що він не порівнює ключі один з одним. Недоліком є ​​те, що він найкраще працює на суміжних послідовностях цілих чисел, в ідеалі на перестановку цілих чисел у тих range(b**m)місцях, де bзазвичай 2. Я додав кілька версій на основі сортування radix після спроби зчитування перерахунку інверсій, офлайн-ортогонального підрахунку діапазону та пов'язаних з цим проблем, що є пов'язані в обчисленні кількості "інверсій" в перестановці .

Щоб ефективно використовувати сортинг radix для підрахунку інверсій в загальній послідовності seqдовжини n, ми можемо створити перестановку, range(n)яка має таку ж кількість інверсій, як seq. Ми можемо це зробити в (в гіршому випадку) O (nlogn) час через TimSort. Хитрість полягає в перестановці індексів seqшляхом сортування seq. Простіше пояснити це невеликим прикладом.

seq = [15, 14, 11, 12, 10, 13]
b = [t[::-1] for t in enumerate(seq)]
print(b)
b.sort()
print(b)

вихід

[(15, 0), (14, 1), (11, 2), (12, 3), (10, 4), (13, 5)]
[(10, 4), (11, 2), (12, 3), (13, 5), (14, 1), (15, 0)]

seqСортувавши пари (значення, індекс), ми переставили індекси seqз однаковою кількістю свопів, які потрібно ввести seqу вихідний порядок із відсортованого порядку. Ми можемо створити цю перестановку шляхом сортування range(n)за допомогою відповідної клавішної функції:

print(sorted(range(len(seq)), key=lambda k: seq[k]))

вихід

[4, 2, 3, 5, 1, 0]

Ми можемо цього уникнути, lambdaвикористовуючи метод seq's .__getitem__:

sorted(range(len(seq)), key=seq.__getitem__)

Це лише трохи швидше, але ми шукаємо всі покращення швидкості, які ми можемо отримати. ;)


Нижче наведений код виконує timeitтести на всіх існуючих на цій сторінці алгоритмів Python, а також декілька моїх власних: кілька версій O (n²) грубої сили, кілька варіацій алгоритму Niklas B і, звичайно, один на основі об'єднання (що я написав, не посилаючись на існуючі відповіді). Він також має мій список дерев на основі дерев, який грубо походить від коду prasadvk, і різні функції, засновані на сортуванні radix, деякі використовують стратегію, подібну до підходів до об'єднання, а деякі використовують sumабо дерево Fenwick.

Ця програма вимірює час виконання кожної функції на ряді випадкових списків цілих чисел; він також може перевірити, що кожна функція дає ті самі результати, що й інші, і що вона не змінює список вводу.

Кожен timeitвиклик дає вектор, що містить 3 результати, які я сортую. Основне значення тут дивіться мінімальну один, інші значення лише дають уявлення про те , як надійності , що мінімальне значення, як описано в примітці в тих timeitмодулях документації .

На жаль, вихід з цієї програми занадто великий, щоб включити її у цю відповідь, тому я розміщую її у власному (вікі спільноти) .

Вихід складається з 3-х прогонів на моїй давній 32-бітній одноядерній 2 ГГц-машині, що працює на Python 3.6.0 на старому Debian-похідному дистрибутиві. YMMV. Під час тестів я вимкнув веб-браузер і відключився від маршрутизатора, щоб мінімізувати вплив інших завдань на процесор.

Перший запуск тестує всі функції з розмірами списку від 5 до 320, розмірами циклу від 4096 до 64 (якщо розмір списку подвоюється, розмір циклу зменшується вдвічі). Випадковий пул, який використовується для побудови кожного списку, удвічі менший за розмір списку, тому ми, ймовірно, отримаємо багато дублікатів. Деякі алгоритми підрахунку інверсії більш чутливі до дублікатів, ніж інші.

У другому виконанні використовуються більші списки: 640 - 10240 і фіксований цикл розміром 8. Для економії часу він виключає кілька найповільніших функцій з тестів. Мій перебором O (N²) функції просто шлях занадто повільно в цих розмірах, і , як згадувалося раніше, мій код , який використовує sum, який робить так добре на малих і середніх списків, просто не може тримати на великих списках.

Фінальний цикл охоплює список розмірів від 20480 до 655360 та фіксований цикл розміром 4, з 8 найшвидшими функціями. Для списків розміром менше 40 000 або більше код Тіма Бабіча є явним переможцем. Молодці Тім! Код Нікласа Б також є хорошим виконавцем за всебічне бажання, хоча його побивають у менших списках. Код "python" на основі розбиття також працює досить добре, хоча, здається, він трохи повільніший з величезними списками з безліччю дублікатів, ймовірно, завдяки тому, що лінійний whileцикл використовується для переходу на дупи.

Однак для дуже великих розмірів списку алгоритми на основі розбиття не можуть конкурувати з справжніми алгоритмами O (nlogn).

#!/usr/bin/env python3

''' Test speeds of various ways of counting inversions in a list

    The inversion count is a measure of how sorted an array is.
    A pair of items in a are inverted if i < j but a[j] > a[i]

    See /programming/337664/counting-inversions-in-an-array

    This program contains code by the following authors:
    mkso
    Niklas B
    B. M.
    Tim Babych
    python
    Zhe Hu
    prasadvk
    noman pouigt
    PM 2Ring

    Timing and verification code by PM 2Ring
    Collated 2017.12.16
    Updated 2017.12.21
'''

from timeit import Timer
from random import seed, randrange
from bisect import bisect, insort_left

seed('A random seed string')

# Merge sort version by mkso
def count_inversion_mkso(lst):
    return merge_count_inversion(lst)[1]

def merge_count_inversion(lst):
    if len(lst) <= 1:
        return lst, 0
    middle = len(lst) // 2
    left, a = merge_count_inversion(lst[:middle])
    right, b = merge_count_inversion(lst[middle:])
    result, c = merge_count_split_inversion(left, right)
    return result, (a + b + c)

def merge_count_split_inversion(left, right):
    result = []
    count = 0
    i, j = 0, 0
    left_len = len(left)
    while i < left_len and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            count += left_len - i
            j += 1
    result += left[i:]
    result += right[j:]
    return result, count

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Using a Binary Indexed Tree, aka a Fenwick tree, by Niklas B.
def count_inversions_NiklasB(a):
    res = 0
    counts = [0] * (len(a) + 1)
    rank = {v: i for i, v in enumerate(sorted(a), 1)}
    for x in reversed(a):
        i = rank[x] - 1
        while i:
            res += counts[i]
            i -= i & -i
        i = rank[x]
        while i <= len(a):
            counts[i] += 1
            i += i & -i
    return res

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by B.M
# Modified by PM 2Ring to deal with the global counter
bm_count = 0

def merge_count_BM(seq):
    global bm_count
    bm_count = 0
    sort_bm(seq)
    return bm_count

def merge_bm(l1,l2):
    global bm_count
    l = []
    while l1 and l2:
        if l1[-1] <= l2[-1]:
            l.append(l2.pop())
        else:
            l.append(l1.pop())
            bm_count += len(l2)
    l.reverse()
    return l1 + l2 + l

def sort_bm(l):
    t = len(l) // 2
    return merge_bm(sort_bm(l[:t]), sort_bm(l[t:])) if t > 0 else l

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Bisection based method by Tim Babych
def solution_TimBabych(A):
    sorted_left = []
    res = 0
    for i in range(1, len(A)):
        insort_left(sorted_left, A[i-1])
        # i is also the length of sorted_left
        res += (i - bisect(sorted_left, A[i]))
    return res

# Slightly faster, except for very small lists
def solutionE_TimBabych(A):
    res = 0
    sorted_left = []
    for i, u in enumerate(A):
        # i is also the length of sorted_left
        res += (i - bisect(sorted_left, u))
        insort_left(sorted_left, u)
    return res

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Bisection based method by "python"
def solution_python(A):
    B = list(A)
    B.sort()
    inversion_count = 0
    for i in range(len(A)):
        j = binarySearch_python(B, A[i])
        while B[j] == B[j - 1]:
            if j < 1:
                break
            j -= 1
        inversion_count += j
        B.pop(j)
    return inversion_count

def binarySearch_python(alist, item):
    first = 0
    last = len(alist) - 1
    found = False
    while first <= last and not found:
        midpoint = (first + last) // 2
        if alist[midpoint] == item:
            return midpoint
        else:
            if item < alist[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by Zhe Hu
def inv_cnt_ZheHu(a):
    _, count = inv_cnt(a.copy())
    return count

def inv_cnt(a):
    n = len(a)
    if n==1:
        return a, 0
    left = a[0:n//2] # should be smaller
    left, cnt1 = inv_cnt(left)
    right = a[n//2:] # should be larger
    right, cnt2 = inv_cnt(right)

    cnt = 0
    i_left = i_right = i_a = 0
    while i_a < n:
        if (i_right>=len(right)) or (i_left < len(left)
            and left[i_left] <= right[i_right]):
            a[i_a] = left[i_left]
            i_left += 1
        else:
            a[i_a] = right[i_right]
            i_right += 1
            if i_left < len(left):
                cnt += len(left) - i_left
        i_a += 1
    return (a, cnt1 + cnt2 + cnt)

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by noman pouigt
# From https://stackoverflow.com/q/47830098
def reversePairs_nomanpouigt(nums):
    def merge(left, right):
        if not left or not right:
            return (0, left + right)
        #if everything in left is less than right
        if left[len(left)-1] < right[0]:
            return (0, left + right)
        else:
            left_idx, right_idx, count = 0, 0, 0
            merged_output = []

            # check for condition before we merge it
            while left_idx < len(left) and right_idx < len(right):
                #if left[left_idx] > 2 * right[right_idx]:
                if left[left_idx] > right[right_idx]:
                    count += len(left) - left_idx
                    right_idx += 1
                else:
                    left_idx += 1

            #merging the sorted list
            left_idx, right_idx = 0, 0
            while left_idx < len(left) and right_idx < len(right):
                if left[left_idx] > right[right_idx]:
                    merged_output += [right[right_idx]]
                    right_idx += 1
                else:
                    merged_output += [left[left_idx]]
                    left_idx += 1
            if left_idx == len(left):
                merged_output += right[right_idx:]
            else:
                merged_output += left[left_idx:]
        return (count, merged_output)

    def partition(nums):
        count = 0
        if len(nums) == 1 or not nums:
            return (0, nums)
        pivot = len(nums)//2
        left_count, l = partition(nums[:pivot])
        right_count, r = partition(nums[pivot:])
        temp_count, temp_list = merge(l, r)
        return (temp_count + left_count + right_count, temp_list)
    return partition(nums)[0]

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# PM 2Ring
def merge_PM2R(seq):
    seq, count = merge_sort_count_PM2R(seq)
    return count

def merge_sort_count_PM2R(seq):
    mid = len(seq) // 2
    if mid == 0:
        return seq, 0
    left, left_total = merge_sort_count_PM2R(seq[:mid])
    right, right_total = merge_sort_count_PM2R(seq[mid:])
    total = left_total + right_total
    result = []
    i = j = 0
    left_len, right_len = len(left), len(right)
    while i < left_len and j < right_len:
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
            total += left_len - i
    result.extend(left[i:])
    result.extend(right[j:])
    return result, total

def rank_sum_PM2R(a):
    total = 0
    counts = [0] * len(a)
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        total += sum(counts[:i])
        counts[i] += 1
    return total

# Fenwick tree functions adapted from C code on Wikipedia
def fen_sum(tree, i):
    ''' Return the sum of the first i elements, 0 through i-1 '''
    total = 0
    while i:
        total += tree[i-1]
        i -= i & -i
    return total

def fen_add(tree, delta, i):
    ''' Add delta to element i and thus 
        to fen_sum(tree, j) for all j > i 
    '''
    size = len(tree)
    while i < size:
        tree[i] += delta
        i += (i+1) & -(i+1)

def fenwick_PM2R(a):
    total = 0
    counts = [0] * len(a)
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        total += fen_sum(counts, i)
        fen_add(counts, 1, i)
    return total

def fenwick_inline_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        j = i + 1
        while i:
            total += counts[i]
            i -= i & -i
        while j < size:
            counts[j] += 1
            j += j & -j
    return total

def bruteforce_loops_PM2R(a):
    total = 0
    for i in range(1, len(a)):
        u = a[i]
        for j in range(i):
            if a[j] > u:
                total += 1
    return total

def bruteforce_sum_PM2R(a):
    return sum(1 for i in range(1, len(a)) for j in range(i) if a[j] > a[i])

# Using binary tree counting, derived from C++ code (?) by prasadvk
# https://stackoverflow.com/a/16056139
def ltree_count_PM2R(a):
    total, root = 0, None
    for u in a:
        # Store data in a list-based tree structure
        # [data, count, left_child, right_child]
        p = [u, 0, None, None]
        if root is None:
            root = p
            continue
        q = root
        while True:
            if p[0] < q[0]:
                total += 1 + q[1]
                child = 2
            else:
                q[1] += 1
                child = 3
            if q[child]:
                q = q[child]
            else:
                q[child] = p
                break
    return total

# Counting based on radix sort, recursive version
def radix_partition_rec(a, L):
    if len(a) < 2:
        return 0
    if len(a) == 2:
        return a[1] < a[0]
    left, right = [], []
    count = 0
    for u in a:
        if u & L:
            right.append(u)
        else:
            count += len(right)
            left.append(u)
    L >>= 1
    if L:
        count += radix_partition_rec(left, L) + radix_partition_rec(right, L)
    return count

# The following functions determine swaps using a permutation of 
# range(len(a)) that has the same inversion count as `a`. We can create
# this permutation with `sorted(range(len(a)), key=lambda k: a[k])`
# but `sorted(range(len(a)), key=a.__getitem__)` is a little faster.

# Counting based on radix sort, iterative version
def radix_partition_iter(seq, L):
    count = 0
    parts = [seq]
    while L and parts:
        newparts = []
        for a in parts:
            if len(a) < 2:
                continue
            if len(a) == 2:
                count += a[1] < a[0]
                continue
            left, right = [], []
            for u in a:
                if u & L:
                    right.append(u)
                else:
                    count += len(right)
                    left.append(u)
            if left:
                newparts.append(left)
            if right:
                newparts.append(right)
        parts = newparts
        L >>= 1
    return count

def perm_radixR_PM2R(a):
    size = len(a)
    b = sorted(range(size), key=a.__getitem__)
    n = size.bit_length() - 1
    return radix_partition_rec(b, 1 << n)

def perm_radixI_PM2R(a):
    size = len(a)
    b = sorted(range(size), key=a.__getitem__)
    n = size.bit_length() - 1
    return radix_partition_iter(b, 1 << n)

# Plain sum of the counts of the permutation
def perm_sum_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    for i in reversed(sorted(range(size), key=a.__getitem__)):
        total += sum(counts[:i])
        counts[i] = 1
    return total

# Fenwick sum of the counts of the permutation
def perm_fenwick_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    for i in reversed(sorted(range(size), key=a.__getitem__)):
        j = i + 1
        while i:
            total += counts[i]
            i -= i & -i
        while j < size:
            counts[j] += 1
            j += j & -j
    return total

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# All the inversion-counting functions
funcs = (
    solution_TimBabych,
    solutionE_TimBabych,
    solution_python,
    count_inversion_mkso,
    count_inversions_NiklasB,
    merge_count_BM,
    inv_cnt_ZheHu,
    reversePairs_nomanpouigt,
    fenwick_PM2R,
    fenwick_inline_PM2R,
    merge_PM2R,
    rank_sum_PM2R,
    bruteforce_loops_PM2R,
    bruteforce_sum_PM2R,
    ltree_count_PM2R,
    perm_radixR_PM2R,
    perm_radixI_PM2R,
    perm_sum_PM2R,
    perm_fenwick_PM2R,
)

def time_test(seq, loops, verify=False):
    orig = seq
    timings = []
    for func in funcs:
        seq = orig.copy()
        value = func(seq) if verify else None
        t = Timer(lambda: func(seq))
        result = sorted(t.repeat(3, loops))
        timings.append((result, func.__name__, value))
        assert seq==orig, 'Sequence altered by {}!'.format(func.__name__)
    first = timings[0][-1]
    timings.sort()
    for result, name, value in timings:
        result = ', '.join([format(u, '.5f') for u in result])
        print('{:24} : {}'.format(name, result))

    if verify:
        # Check that all results are identical
        bad = ['%s: %d' % (name, value)
            for _, name, value in timings if value != first]
        if bad:
            print('ERROR. Value: {}, bad: {}'.format(first, ', '.join(bad)))
        else:
            print('Value: {}'.format(first))
    print()

#Run the tests
size, loops = 5, 1 << 12
verify = True
for _ in range(7):
    hi = size // 2
    print('Size = {}, hi = {}, {} loops'.format(size, hi, loops))
    seq = [randrange(hi) for _ in range(size)]
    time_test(seq, loops, verify)
    loops >>= 1
    size <<= 1

#size, loops = 640, 8
#verify = False
#for _ in range(5):
    #hi = size // 2
    #print('Size = {}, hi = {}, {} loops'.format(size, hi, loops))
    #seq = [randrange(hi) for _ in range(size)]
    #time_test(seq, loops, verify)
    #size <<= 1

#size, loops = 163840, 4
#verify = False
#for _ in range(3):
    #hi = size // 2
    #print('Size = {}, hi = {}, {} loops'.format(size, hi, loops))
    #seq = [randrange(hi) for _ in range(size)]
    #time_test(seq, loops, verify)
    #size <<= 1

Будь ласка, дивіться тут для виводу


Дякую, це було дуже цікаво :) Чітко показує переваги використання модуля C - який бісект.
Тім Бабіч

Проблема полягає в тому, що переможець використовує (теоретично) квадратичний алгоритм. для розміру ~ 100 000, його будуть бити інші. Я відредагував свою посаду, щоб поставити пітон-квазілінійне рішення C-швидкості.
БМ

@ BM Безумовно, але бісектний підхід Тіма досить хороший, поки ви не досягнете розміру в 45 000 або близько того. У мене є ще кілька рішень, які я додам сюди наступного дня.
PM 2Ring

@TimBabych Ви кажете, що bisectце C? Я майже впевнений, що це Python.
Стефан Похман


10

Кількість інверсій можна дізнатися, проаналізувавши процес злиття у сортуванні злиття: процес злиття

При копіюванні елемента з другого масиву в масив злиття (9 у цьому прикладі) він зберігає своє місце відносно інших елементів. При копіюванні елемента з першого масиву в масив злиття (5 тут) він інвертується з усіма елементами, що перебувають у другому масиві (2 інверсії з 3 і 4). Тож невелика модифікація сортування злиття може вирішити проблему в O (n ln n).
Для прикладу, просто коментуйте два # рядки в коді пігтона злиття, щоб мати підрахунок.

def merge(l1,l2):
    l = []
    # global count
    while l1 and l2:
        if l1[-1] <= l2[-1]:
            l.append(l2.pop())
        else:
            l.append(l1.pop())
            # count += len(l2)
    l.reverse()
    return l1 + l2 + l

def sort(l): 
    t = len(l) // 2
    return merge(sort(l[:t]), sort(l[t:])) if t > 0 else l

count=0
print(sort([5,1,2,4,9,3]), count)
# [1, 2, 3, 4, 5, 9] 6

РЕДАКТ 1

Це ж завдання можна досягти і зі стабільною версією швидкого сортування, яка, як відомо, трохи швидше:

def part(l):
    pivot=l[-1]
    small,big = [],[]
    count = big_count = 0
    for x in l:
        if x <= pivot:
            small.append(x)
            count += big_count
        else:
            big.append(x)
            big_count += 1
    return count,small,big

def quick_count(l):
    if len(l)<2 : return 0
    count,small,big = part(l)
    small.pop()
    return count + quick_count(small) + quick_count(big)

Вибираючи стрижневий елемент як останній елемент, інверсії добре підраховуються, а час виконання на 40% краще, ніж злиття вище.

EDIT 2

Для виконання в python, numpy & numba версії:

Спочатку нумерована частина, в якій використовується argsort O (n ln n):

def count_inversions(a):
    n = a.size
    counts = np.arange(n) & -np.arange(n)  # The BIT
    ags = a.argsort(kind='mergesort')    
    return  BIT(ags,counts,n)

І частина numba для ефективного BIT підходу :

@numba.njit
def BIT(ags,counts,n):
    res = 0        
    for x in ags :
        i = x
        while i:
            res += counts[i]
            i -= i & -i
        i = x+1
        while i < n:
            counts[i] -= 1
            i += i & -i
    return  res  

Я опублікував відповідь , яка timeitзіставляє всі відповіді Python на це питання, тому він включає ваш код. Можливо, вам буде цікаво переглянути результати часу.
PM 2Ring

У цій публікації немає питань щодо продуктивності ... Я спробую через деякий час. Numpy numba дозволено;)?
БМ

Я ніколи не використовував Numba, але я трохи використовував Numpy, і думав додати версію Numpy сам, але вирішив просто обмежити тести рішеннями, які використовують лише стандартну бібліотеку. Але я думаю, було б цікаво подивитися, як Numpy порівнює рішення. Я підозрюю, що в невеликих списках це не буде швидше.
PM 2Ring

Швидкість у 100 разів вражає! Але я не можу це запустити, оскільки у мене немає Numba. І як я вже говорив раніше, було б справедливо включати його до своєї timeitколекції.
PM 2Ring

8

Зауважте, що відповідь Джеффрі Ірвінга є неправильною.

Кількість інверсій в масиві становить половину загальної відстані елементів, які необхідно перемістити, щоб сортувати масив. Тому його можна обчислити, сортуючи масив, підтримуючи отриману перестановку p [i], а потім обчисливши суму abs (p [i] -i) / 2. Це займає час O (n log n), що є оптимальним.

Альтернативний метод наведено на веб- сайті http://mathworld.wolfram.com/PermutationInversion.html . Цей спосіб еквівалентний сумі max (0, p [i] -i), яка дорівнює сумі abs (p [i] -i]) / 2, оскільки загальна відстань переміщення елементів вліво дорівнює загальна відстань елементів рухається праворуч.

Візьмемо для прикладу послідовність {3, 2, 1}. Існує три інверсії: (3, 2), (3, 1), (2, 1), тому число інверсії - 3. Однак, згідно з цитованим методом, відповідь була б 2.


Натомість правильну відповідь можна знайти, підрахувавши мінімально необхідну кількість сусідніх свопів. Дивіться дискусію: stackoverflow.com/questions/20990127/…
Ісаак Тернер


4

Ось одне можливе рішення з варіацією бінарного дерева. Він додає поле під назвою rightSubTreeSize до кожного вузла дерева. Продовжуйте вставляти число у двійкове дерево у тому порядку, в якому вони відображаються у масиві. Якщо число буде lhs вузла, то число інверсії для цього елемента буде (1 + rightSubTreeSize). Оскільки всі ці елементи більше, ніж поточний, і вони з'являлися б раніше в масиві. Якщо елемент переходить до rhs вузла, просто збільште його rightSubTreeSize. Далі йде код.

Node { 
    int data;
    Node* left, *right;
    int rightSubTreeSize;

    Node(int data) { 
        rightSubTreeSize = 0;
    }   
};

Node* root = null;
int totCnt = 0;
for(i = 0; i < n; ++i) { 
    Node* p = new Node(a[i]);
    if(root == null) { 
        root = p;
        continue;
    } 

    Node* q = root;
    int curCnt = 0;
    while(q) { 
        if(p->data <= q->data) { 
            curCnt += 1 + q->rightSubTreeSize;
            if(q->left) { 
                q = q->left;
            } else { 
                q->left = p;
                break;
            }
        } else { 
            q->rightSubTreeSize++;
            if(q->right) { 
                q = q->right;
            } else { 
                q->right = p;
                break;
            }
        }
    }

    totCnt += curCnt;
  }
  return totCnt;

Це цікавий підхід, і він виявляється досить швидким. Однак це порівняння має бути, if(p->data < q->data)інакше дублікати неправильно обробляються. І немає необхідності тестувати qвгорі петлі, безумовна whileпетля працює чудово. Також ви нехтували згадкою, що це за мова. :) І ваша функція, схоже, втратила рядок заголовка.
PM 2Ring

Щойно я додав у свою відповідь версію Python на основі алгоритму вашого дерева. Звичайно, це не так швидко, як повністю складена версія, але це досить добре, порівняно з іншими версіями Python.
PM 2Ring

3
public static int mergeSort(int[] a, int p, int r)
{
    int countInversion = 0;
    if(p < r)
    {
        int q = (p + r)/2;
        countInversion = mergeSort(a, p, q);
        countInversion += mergeSort(a, q+1, r);
        countInversion += merge(a, p, q, r);
    }
    return countInversion;
}

public static int merge(int[] a, int p, int q, int r)
{
    //p=0, q=1, r=3
    int countingInversion = 0;
    int n1 = q-p+1;
    int n2 = r-q;
    int[] temp1 = new int[n1+1];
    int[] temp2 = new int[n2+1];
    for(int i=0; i<n1; i++) temp1[i] = a[p+i];
    for(int i=0; i<n2; i++) temp2[i] = a[q+1+i];

    temp1[n1] = Integer.MAX_VALUE;
    temp2[n2] = Integer.MAX_VALUE;
    int i = 0, j = 0;

    for(int k=p; k<=r; k++)
    {
        if(temp1[i] <= temp2[j])
        {
            a[k] = temp1[i];
            i++;
        }
        else
        {
            a[k] = temp2[j];
            j++;
            countingInversion=countingInversion+(n1-i); 
        }
    }
    return countingInversion;
}
public static void main(String[] args)
{
    int[] a = {1, 20, 6, 4, 5};
    int countInversion = mergeSort(a, 0, a.length-1);
    System.out.println(countInversion);
}

3
Чи це сильно відрізняється від уже розміщених рішень Java та Python ? Крім того, відповіді, що стосуються лише коду, не є особливо хорошими ІМО, особливо враховуючи, що це питання навіть не вказало мову.
Бернхард Баркер

2

Оскільки це старе запитання, я дам свою відповідь у С.

#include <stdio.h>

int count = 0;
int inversions(int a[], int len);
void mergesort(int a[], int left, int right);
void merge(int a[], int left, int mid, int right);

int main() {
  int a[] = { 1, 5, 2, 4, 0 };
  printf("%d\n", inversions(a, 5));
}

int inversions(int a[], int len) {
  mergesort(a, 0, len - 1);
  return count;
}

void mergesort(int a[], int left, int right) {
  if (left < right) {
     int mid = (left + right) / 2;
     mergesort(a, left, mid);
     mergesort(a, mid + 1, right);
     merge(a, left, mid, right);
  }
}

void merge(int a[], int left, int mid, int right) {
  int i = left;
  int j = mid + 1;
  int k = 0;
  int b[right - left + 1];
  while (i <= mid && j <= right) {
     if (a[i] <= a[j]) {
       b[k++] = a[i++];
     } else {
       printf("right element: %d\n", a[j]);
       count += (mid - i + 1);
       printf("new count: %d\n", count);
       b[k++] = a[j++];
     }
  }
  while (i <= mid)
    b[k++] = a[i++];
  while (j <= right)
    b[k++] = a[j++];
  for (i = left, k = 0; i <= right; i++, k++) {
    a[i] = b[k];
  }
}

-1 тому, що відповідь будь-якою іншою мовою призведе до безнадійно занадто багато відповідей, всі вони по суті дублюють інформацію, вже представлену в інших відповідях. Крім того, це, по суті, відповідь, що стосується лише коду, без пояснень, що, в кращому випадку, в основному підходить для запитань про цю мову.
Бернхард Баркер

2

Ось рішення c ++

/**
*array sorting needed to verify if first arrays n'th element is greater than sencond arrays
*some element then all elements following n will do the same
*/
#include<stdio.h>
#include<iostream>
using namespace std;
int countInversions(int array[],int size);
int merge(int arr1[],int size1,int arr2[],int size2,int[]);
int main()
{
    int array[] = {2, 4, 1, 3, 5};
    int size = sizeof(array) / sizeof(array[0]);
    int x = countInversions(array,size);
    printf("number of inversions = %d",x);
}

int countInversions(int array[],int size)
{
    if(size > 1 )
    {
    int mid = size / 2;
    int count1 = countInversions(array,mid);
    int count2 = countInversions(array+mid,size-mid);
    int temp[size];
    int count3 = merge(array,mid,array+mid,size-mid,temp);
    for(int x =0;x<size ;x++)
    {
        array[x] = temp[x];
    }
    return count1 + count2 + count3;
    }else{
        return 0;
    }
}

int merge(int arr1[],int size1,int arr2[],int size2,int temp[])
{
    int count  = 0;
    int a = 0;
    int b = 0;
    int c = 0;
    while(a < size1 && b < size2)
    {
        if(arr1[a] < arr2[b])
        {
            temp[c] = arr1[a];
            c++;
            a++;
        }else{
            temp[c] = arr2[b];
            b++;
            c++;
            count = count + size1 -a;
        }
    }

    while(a < size1)
    {
        temp[c] = arr1[a];
        c++;a++;
    }

while(b < size2)
    {
        temp[c] = arr2[b];
        c++;b++;
    }

    return count;
}

2

Ця відповідь містить результати timeitтестів, що виробляються кодом у моїй головній відповіді . Будь ласка, дивіться цю відповідь для деталей!

count_inversions speed test results

Size = 5, hi = 2, 4096 loops
ltree_count_PM2R         : 0.04871, 0.04872, 0.04876
bruteforce_loops_PM2R    : 0.05696, 0.05700, 0.05776
solution_TimBabych       : 0.05760, 0.05822, 0.05943
solutionE_TimBabych      : 0.06642, 0.06704, 0.06760
bruteforce_sum_PM2R      : 0.07523, 0.07545, 0.07563
perm_sum_PM2R            : 0.09873, 0.09875, 0.09935
rank_sum_PM2R            : 0.10449, 0.10463, 0.10468
solution_python          : 0.13034, 0.13061, 0.13221
fenwick_inline_PM2R      : 0.14323, 0.14610, 0.18802
perm_radixR_PM2R         : 0.15146, 0.15203, 0.15235
merge_count_BM           : 0.16179, 0.16267, 0.16467
perm_radixI_PM2R         : 0.16200, 0.16202, 0.16768
perm_fenwick_PM2R        : 0.16887, 0.16920, 0.17075
merge_PM2R               : 0.18262, 0.18271, 0.18418
count_inversions_NiklasB : 0.19183, 0.19279, 0.20388
count_inversion_mkso     : 0.20060, 0.20141, 0.20398
inv_cnt_ZheHu            : 0.20815, 0.20841, 0.20906
fenwick_PM2R             : 0.22109, 0.22137, 0.22379
reversePairs_nomanpouigt : 0.29620, 0.29689, 0.30293
Value: 5

Size = 10, hi = 5, 2048 loops
solution_TimBabych       : 0.05954, 0.05989, 0.05991
solutionE_TimBabych      : 0.05970, 0.05972, 0.05998
perm_sum_PM2R            : 0.07517, 0.07519, 0.07520
ltree_count_PM2R         : 0.07672, 0.07677, 0.07684
bruteforce_loops_PM2R    : 0.07719, 0.07724, 0.07817
rank_sum_PM2R            : 0.08587, 0.08823, 0.08864
bruteforce_sum_PM2R      : 0.09470, 0.09472, 0.09484
solution_python          : 0.13126, 0.13154, 0.13185
perm_radixR_PM2R         : 0.14239, 0.14320, 0.14474
perm_radixI_PM2R         : 0.14632, 0.14669, 0.14679
fenwick_inline_PM2R      : 0.16796, 0.16831, 0.17030
perm_fenwick_PM2R        : 0.18189, 0.18212, 0.18638
merge_count_BM           : 0.19816, 0.19870, 0.19948
count_inversions_NiklasB : 0.21807, 0.22031, 0.22215
merge_PM2R               : 0.22037, 0.22048, 0.26106
fenwick_PM2R             : 0.24290, 0.24314, 0.24744
count_inversion_mkso     : 0.24895, 0.24899, 0.25205
inv_cnt_ZheHu            : 0.26253, 0.26259, 0.26590
reversePairs_nomanpouigt : 0.35711, 0.35762, 0.35973
Value: 20

Size = 20, hi = 10, 1024 loops
solutionE_TimBabych      : 0.05687, 0.05696, 0.05720
solution_TimBabych       : 0.06126, 0.06151, 0.06168
perm_sum_PM2R            : 0.06875, 0.06906, 0.07054
rank_sum_PM2R            : 0.07988, 0.07995, 0.08002
ltree_count_PM2R         : 0.11232, 0.11239, 0.11257
bruteforce_loops_PM2R    : 0.12553, 0.12584, 0.12592
solution_python          : 0.13472, 0.13540, 0.13694
bruteforce_sum_PM2R      : 0.15820, 0.15849, 0.16021
perm_radixI_PM2R         : 0.17101, 0.17148, 0.17229
perm_radixR_PM2R         : 0.17891, 0.18087, 0.18366
perm_fenwick_PM2R        : 0.20554, 0.20708, 0.21412
fenwick_inline_PM2R      : 0.21161, 0.21163, 0.22047
merge_count_BM           : 0.24125, 0.24261, 0.24565
count_inversions_NiklasB : 0.25712, 0.25754, 0.25778
merge_PM2R               : 0.26477, 0.26566, 0.31297
fenwick_PM2R             : 0.28178, 0.28216, 0.29069
count_inversion_mkso     : 0.30286, 0.30290, 0.30652
inv_cnt_ZheHu            : 0.32024, 0.32041, 0.32447
reversePairs_nomanpouigt : 0.45812, 0.45822, 0.46172
Value: 98

Size = 40, hi = 20, 512 loops
solutionE_TimBabych      : 0.05784, 0.05787, 0.05958
solution_TimBabych       : 0.06452, 0.06475, 0.06479
perm_sum_PM2R            : 0.07254, 0.07261, 0.07263
rank_sum_PM2R            : 0.08537, 0.08540, 0.08572
ltree_count_PM2R         : 0.11744, 0.11749, 0.11792
solution_python          : 0.14262, 0.14285, 0.14465
perm_radixI_PM2R         : 0.18774, 0.18776, 0.18922
perm_radixR_PM2R         : 0.19425, 0.19435, 0.19609
bruteforce_loops_PM2R    : 0.21500, 0.21511, 0.21686
perm_fenwick_PM2R        : 0.23338, 0.23375, 0.23674
fenwick_inline_PM2R      : 0.24947, 0.24958, 0.25189
bruteforce_sum_PM2R      : 0.27627, 0.27646, 0.28041
merge_count_BM           : 0.28059, 0.28128, 0.28294
count_inversions_NiklasB : 0.28557, 0.28759, 0.29022
merge_PM2R               : 0.29886, 0.29928, 0.30317
fenwick_PM2R             : 0.30241, 0.30259, 0.35237
count_inversion_mkso     : 0.34252, 0.34356, 0.34441
inv_cnt_ZheHu            : 0.37468, 0.37569, 0.37847
reversePairs_nomanpouigt : 0.50725, 0.50770, 0.50943
Value: 369

Size = 80, hi = 40, 256 loops
solutionE_TimBabych      : 0.06339, 0.06373, 0.06513
solution_TimBabych       : 0.06984, 0.06994, 0.07009
perm_sum_PM2R            : 0.09171, 0.09172, 0.09186
rank_sum_PM2R            : 0.10468, 0.10474, 0.10500
ltree_count_PM2R         : 0.14416, 0.15187, 0.18541
solution_python          : 0.17415, 0.17423, 0.17451
perm_radixI_PM2R         : 0.20676, 0.20681, 0.20936
perm_radixR_PM2R         : 0.21671, 0.21695, 0.21736
perm_fenwick_PM2R        : 0.26197, 0.26252, 0.26264
fenwick_inline_PM2R      : 0.28111, 0.28249, 0.28382
count_inversions_NiklasB : 0.31746, 0.32448, 0.32451
merge_count_BM           : 0.31964, 0.33842, 0.35276
merge_PM2R               : 0.32890, 0.32941, 0.33322
fenwick_PM2R             : 0.34355, 0.34377, 0.34873
count_inversion_mkso     : 0.37689, 0.37698, 0.38079
inv_cnt_ZheHu            : 0.42923, 0.42941, 0.43249
bruteforce_loops_PM2R    : 0.43544, 0.43601, 0.43902
bruteforce_sum_PM2R      : 0.52106, 0.52160, 0.52531
reversePairs_nomanpouigt : 0.57805, 0.58156, 0.58252
Value: 1467

Size = 160, hi = 80, 128 loops
solutionE_TimBabych      : 0.06766, 0.06784, 0.06963
solution_TimBabych       : 0.07433, 0.07489, 0.07516
perm_sum_PM2R            : 0.13143, 0.13175, 0.13179
rank_sum_PM2R            : 0.14428, 0.14440, 0.14922
solution_python          : 0.20072, 0.20076, 0.20084
ltree_count_PM2R         : 0.20314, 0.20583, 0.24776
perm_radixI_PM2R         : 0.23061, 0.23078, 0.23525
perm_radixR_PM2R         : 0.23894, 0.23915, 0.24234
perm_fenwick_PM2R        : 0.30984, 0.31181, 0.31503
fenwick_inline_PM2R      : 0.31933, 0.32680, 0.32722
merge_count_BM           : 0.36003, 0.36387, 0.36409
count_inversions_NiklasB : 0.36796, 0.36814, 0.37106
merge_PM2R               : 0.36847, 0.36848, 0.37127
fenwick_PM2R             : 0.37833, 0.37847, 0.38095
count_inversion_mkso     : 0.42746, 0.42747, 0.43184
inv_cnt_ZheHu            : 0.48969, 0.48974, 0.49293
reversePairs_nomanpouigt : 0.67791, 0.68157, 0.72420
bruteforce_loops_PM2R    : 0.82816, 0.83175, 0.83282
bruteforce_sum_PM2R      : 1.03322, 1.03378, 1.03562
Value: 6194

Size = 320, hi = 160, 64 loops
solutionE_TimBabych      : 0.07467, 0.07470, 0.07483
solution_TimBabych       : 0.08036, 0.08066, 0.08077
perm_sum_PM2R            : 0.21142, 0.21201, 0.25766
solution_python          : 0.22410, 0.22644, 0.22897
rank_sum_PM2R            : 0.22820, 0.22851, 0.22877
ltree_count_PM2R         : 0.24424, 0.24595, 0.24645
perm_radixI_PM2R         : 0.25690, 0.25710, 0.26191
perm_radixR_PM2R         : 0.26501, 0.26504, 0.26729
perm_fenwick_PM2R        : 0.33483, 0.33507, 0.33845
fenwick_inline_PM2R      : 0.34413, 0.34484, 0.35153
merge_count_BM           : 0.39875, 0.39919, 0.40302
fenwick_PM2R             : 0.40434, 0.40439, 0.40845
merge_PM2R               : 0.40814, 0.41531, 0.51417
count_inversions_NiklasB : 0.41681, 0.42009, 0.42128
count_inversion_mkso     : 0.47132, 0.47192, 0.47385
inv_cnt_ZheHu            : 0.54468, 0.54750, 0.54893
reversePairs_nomanpouigt : 0.76164, 0.76389, 0.80357
bruteforce_loops_PM2R    : 1.59125, 1.60430, 1.64131
bruteforce_sum_PM2R      : 2.03734, 2.03834, 2.03975
Value: 24959

Run 2

Size = 640, hi = 320, 8 loops
solutionE_TimBabych      : 0.04135, 0.04374, 0.04575
ltree_count_PM2R         : 0.06738, 0.06758, 0.06874
perm_radixI_PM2R         : 0.06928, 0.06943, 0.07019
fenwick_inline_PM2R      : 0.07850, 0.07856, 0.08059
perm_fenwick_PM2R        : 0.08151, 0.08162, 0.08170
perm_sum_PM2R            : 0.09122, 0.09133, 0.09221
rank_sum_PM2R            : 0.09549, 0.09603, 0.11270
merge_count_BM           : 0.10733, 0.10807, 0.11032
count_inversions_NiklasB : 0.12460, 0.19865, 0.20205
solution_python          : 0.13514, 0.13585, 0.13814

Size = 1280, hi = 640, 8 loops
solutionE_TimBabych      : 0.04714, 0.04742, 0.04752
perm_radixI_PM2R         : 0.15325, 0.15388, 0.15525
solution_python          : 0.15709, 0.15715, 0.16076
fenwick_inline_PM2R      : 0.16048, 0.16160, 0.16403
ltree_count_PM2R         : 0.16213, 0.16238, 0.16428
perm_fenwick_PM2R        : 0.16408, 0.16416, 0.16449
count_inversions_NiklasB : 0.19755, 0.19833, 0.19897
merge_count_BM           : 0.23736, 0.23793, 0.23912
perm_sum_PM2R            : 0.32946, 0.32969, 0.33277
rank_sum_PM2R            : 0.34637, 0.34756, 0.34858

Size = 2560, hi = 1280, 8 loops
solutionE_TimBabych      : 0.10898, 0.11005, 0.11025
perm_radixI_PM2R         : 0.33345, 0.33352, 0.37656
ltree_count_PM2R         : 0.34670, 0.34786, 0.34833
perm_fenwick_PM2R        : 0.34816, 0.34879, 0.35214
fenwick_inline_PM2R      : 0.36196, 0.36455, 0.36741
solution_python          : 0.36498, 0.36637, 0.40887
count_inversions_NiklasB : 0.42274, 0.42745, 0.42995
merge_count_BM           : 0.50799, 0.50898, 0.50917
perm_sum_PM2R            : 1.27773, 1.27897, 1.27951
rank_sum_PM2R            : 1.29728, 1.30389, 1.30448

Size = 5120, hi = 2560, 8 loops
solutionE_TimBabych      : 0.26914, 0.26993, 0.27253
perm_radixI_PM2R         : 0.71416, 0.71634, 0.71753
perm_fenwick_PM2R        : 0.71976, 0.72078, 0.72078
fenwick_inline_PM2R      : 0.72776, 0.72804, 0.73143
ltree_count_PM2R         : 0.81972, 0.82043, 0.82290
solution_python          : 0.83714, 0.83756, 0.83962
count_inversions_NiklasB : 0.87282, 0.87395, 0.92087
merge_count_BM           : 1.09496, 1.09584, 1.10207
rank_sum_PM2R            : 5.02564, 5.06277, 5.06666
perm_sum_PM2R            : 5.09088, 5.12999, 5.13512

Size = 10240, hi = 5120, 8 loops
solutionE_TimBabych      : 0.71556, 0.71718, 0.72201
perm_radixI_PM2R         : 1.54785, 1.55096, 1.55515
perm_fenwick_PM2R        : 1.55103, 1.55353, 1.59298
fenwick_inline_PM2R      : 1.57118, 1.57240, 1.57271
ltree_count_PM2R         : 1.76240, 1.76247, 1.80944
count_inversions_NiklasB : 1.86543, 1.86851, 1.87208
solution_python          : 2.01490, 2.01519, 2.06423
merge_count_BM           : 2.35215, 2.35301, 2.40023
rank_sum_PM2R            : 20.07048, 20.08399, 20.13200
perm_sum_PM2R            : 20.10187, 20.12551, 20.12683

Run 3
Size = 20480, hi = 10240, 4 loops
solutionE_TimBabych      : 1.07636, 1.08243, 1.09569
perm_radixI_PM2R         : 1.59579, 1.60519, 1.61785
perm_fenwick_PM2R        : 1.66885, 1.68549, 1.71109
fenwick_inline_PM2R      : 1.72073, 1.72752, 1.77217
ltree_count_PM2R         : 1.96900, 1.97820, 2.02578
count_inversions_NiklasB : 2.03257, 2.05005, 2.18548
merge_count_BM           : 2.46768, 2.47377, 2.52133
solution_python          : 2.49833, 2.50179, 3.79819

Size = 40960, hi = 20480, 4 loops
solutionE_TimBabych      : 3.51733, 3.52008, 3.56996
perm_radixI_PM2R         : 3.51736, 3.52365, 3.56459
perm_fenwick_PM2R        : 3.76097, 3.80900, 3.87974
fenwick_inline_PM2R      : 3.95099, 3.96300, 3.99748
ltree_count_PM2R         : 4.49866, 4.54652, 5.39716
count_inversions_NiklasB : 4.61851, 4.64303, 4.73026
merge_count_BM           : 5.31945, 5.35378, 5.35951
solution_python          : 6.78756, 6.82911, 6.98217

Size = 81920, hi = 40960, 4 loops
perm_radixI_PM2R         : 7.68723, 7.71986, 7.72135
perm_fenwick_PM2R        : 8.52404, 8.53349, 8.53710
fenwick_inline_PM2R      : 8.97082, 8.97561, 8.98347
ltree_count_PM2R         : 10.01142, 10.01426, 10.03216
count_inversions_NiklasB : 10.60807, 10.62424, 10.70425
merge_count_BM           : 11.42149, 11.42342, 11.47003
solutionE_TimBabych      : 12.83390, 12.83485, 12.89747
solution_python          : 19.66092, 19.67067, 20.72204

Size = 163840, hi = 81920, 4 loops
perm_radixI_PM2R         : 17.14153, 17.16885, 17.22240
perm_fenwick_PM2R        : 19.25944, 19.27844, 20.27568
fenwick_inline_PM2R      : 19.78221, 19.80219, 19.80766
ltree_count_PM2R         : 22.42240, 22.43259, 22.48837
count_inversions_NiklasB : 22.97341, 23.01516, 23.98052
merge_count_BM           : 24.42683, 24.48559, 24.51488
solutionE_TimBabych      : 60.96006, 61.20145, 63.71835
solution_python          : 73.75132, 73.79854, 73.95874

Size = 327680, hi = 163840, 4 loops
perm_radixI_PM2R         : 36.56715, 36.60221, 37.05071
perm_fenwick_PM2R        : 42.21616, 42.21838, 42.26053
fenwick_inline_PM2R      : 43.04987, 43.09075, 43.13287
ltree_count_PM2R         : 49.87400, 50.08509, 50.69292
count_inversions_NiklasB : 50.74591, 50.75012, 50.75551
merge_count_BM           : 52.37284, 52.51491, 53.43003
solutionE_TimBabych      : 373.67198, 377.03341, 377.42360
solution_python          : 411.69178, 411.92691, 412.83856

Size = 655360, hi = 327680, 4 loops
perm_radixI_PM2R         : 78.51927, 78.66327, 79.46325
perm_fenwick_PM2R        : 90.64711, 90.80328, 91.76126
fenwick_inline_PM2R      : 93.32482, 93.39086, 94.28880
count_inversions_NiklasB : 107.74393, 107.80036, 108.71443
ltree_count_PM2R         : 109.11328, 109.23592, 110.18247
merge_count_BM           : 111.05633, 111.07840, 112.05861
solutionE_TimBabych      : 1830.46443, 1836.39960, 1849.53918
solution_python          : 1911.03692, 1912.04484, 1914.69786

1

Ось код С для інверсій лічильника

#include <stdio.h>
#include <stdlib.h>

int  _mergeSort(int arr[], int temp[], int left, int right);
int merge(int arr[], int temp[], int left, int mid, int right);

/* This function sorts the input array and returns the
   number of inversions in the array */
int mergeSort(int arr[], int array_size)
{
    int *temp = (int *)malloc(sizeof(int)*array_size);
    return _mergeSort(arr, temp, 0, array_size - 1);
}

/* An auxiliary recursive function that sorts the input array and
  returns the number of inversions in the array. */
int _mergeSort(int arr[], int temp[], int left, int right)
{
  int mid, inv_count = 0;
  if (right > left)
  {
    /* Divide the array into two parts and call _mergeSortAndCountInv()
       for each of the parts */
    mid = (right + left)/2;

    /* Inversion count will be sum of inversions in left-part, right-part
      and number of inversions in merging */
    inv_count  = _mergeSort(arr, temp, left, mid);
    inv_count += _mergeSort(arr, temp, mid+1, right);

    /*Merge the two parts*/
    inv_count += merge(arr, temp, left, mid+1, right);
  }
  return inv_count;
}

/* This funt merges two sorted arrays and returns inversion count in
   the arrays.*/
int merge(int arr[], int temp[], int left, int mid, int right)
{
  int i, j, k;
  int inv_count = 0;

  i = left; /* i is index for left subarray*/
  j = mid;  /* i is index for right subarray*/
  k = left; /* i is index for resultant merged subarray*/
  while ((i <= mid - 1) && (j <= right))
  {
    if (arr[i] <= arr[j])
    {
      temp[k++] = arr[i++];
    }
    else
    {
      temp[k++] = arr[j++];

     /*this is tricky -- see above explanation/diagram for merge()*/
      inv_count = inv_count + (mid - i);
    }
  }

  /* Copy the remaining elements of left subarray
   (if there are any) to temp*/
  while (i <= mid - 1)
    temp[k++] = arr[i++];

  /* Copy the remaining elements of right subarray
   (if there are any) to temp*/
  while (j <= right)
    temp[k++] = arr[j++];

  /*Copy back the merged elements to original array*/
  for (i=left; i <= right; i++)
    arr[i] = temp[i];

  return inv_count;
}

/* Driver progra to test above functions */
int main(int argv, char** args)
{
  int arr[] = {1, 20, 6, 4, 5};
  printf(" Number of inversions are %d \n", mergeSort(arr, 5));
  getchar();
  return 0;
}

Пояснення було детально дано тут: http://www.geeksforgeeks.org/counting-inversions/


1

O (n log n) час, O (n) космічне рішення в java.

Злиття, з налаштуванням, щоб зберегти кількість інверсій, виконаних під час кроку злиття. (для добре поясненого злиття погляньте на http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html )

Оскільки злиття може бути зроблене на місці, складність простору може бути поліпшена до O (1).

При використанні цього сорту інверсії трапляються лише на етапі злиття і лише тоді, коли нам потрібно поставити елемент другої частини перед елементами з першої половини, наприклад

  • 0 5 10 15

злилися з

  • 1 6 22

маємо 3 + 2 + 0 = 5 інверсій:

  • 1 з {5, 10, 15}
  • 6 з {10, 15}
  • 22 з {}

Після того як ми зробили 5 перетворень, наш новий об'єднаний список 0, 1, 5, 6, 10, 15, 22

На Codility є демонстраційне завдання під назвою ArrayInversionCount, де ви можете протестувати своє рішення.

    public class FindInversions {

    public static int solution(int[] input) {
        if (input == null)
            return 0;
        int[] helper = new int[input.length];
        return mergeSort(0, input.length - 1, input, helper);
    }

    public static int mergeSort(int low, int high, int[] input, int[] helper) {
        int inversionCount = 0;
        if (low < high) {
            int medium = low + (high - low) / 2;
            inversionCount += mergeSort(low, medium, input, helper);
            inversionCount += mergeSort(medium + 1, high, input, helper);
            inversionCount += merge(low, medium, high, input, helper);
        }
        return inversionCount;
    }

    public static int merge(int low, int medium, int high, int[] input, int[] helper) {
        int inversionCount = 0;

        for (int i = low; i <= high; i++)
            helper[i] = input[i];

        int i = low;
        int j = medium + 1;
        int k = low;

        while (i <= medium && j <= high) {
            if (helper[i] <= helper[j]) {
                input[k] = helper[i];
                i++;
            } else {
                input[k] = helper[j];
                // the number of elements in the first half which the j element needs to jump over.
                // there is an inversion between each of those elements and j.
                inversionCount += (medium + 1 - i);
                j++;
            }
            k++;
        }

        // finish writing back in the input the elements from the first part
        while (i <= medium) {
            input[k] = helper[i];
            i++;
            k++;
        }
        return inversionCount;
    }

}

1

Ось реалізація perl O (n * log (n)):

sub sort_and_count {
    my ($arr, $n) = @_;
    return ($arr, 0) unless $n > 1;

    my $mid = $n % 2 == 1 ? ($n-1)/2 : $n/2;
    my @left = @$arr[0..$mid-1];
    my @right = @$arr[$mid..$n-1];

    my ($sleft, $x) = sort_and_count( \@left, $mid );
    my ($sright, $y) = sort_and_count( \@right, $n-$mid);
    my ($merged, $z) = merge_and_countsplitinv( $sleft, $sright, $n );

    return ($merged, $x+$y+$z);
}

sub merge_and_countsplitinv {
    my ($left, $right, $n) = @_;

    my ($l_c, $r_c) = ($#$left+1, $#$right+1);
    my ($i, $j) = (0, 0);
    my @merged;
    my $inv = 0;

    for my $k (0..$n-1) {
        if ($i<$l_c && $j<$r_c) {
            if ( $left->[$i] < $right->[$j]) {
                push @merged, $left->[$i];
                $i+=1;
            } else {
                push @merged, $right->[$j];
                $j+=1;
                $inv += $l_c - $i;
            }
        } else {
            if ($i>=$l_c) {
                push @merged, @$right[ $j..$#$right ];
            } else {
                push @merged, @$left[ $i..$#$left ];
            }
            last;
        }
    }

    return (\@merged, $inv);
}

1

Моя відповідь на Python:

1- Сортуйте Масив спочатку та зробіть його копію. У моїй програмі B представляє відсортований масив. 2- Ітерація над початковим масивом (несортованим) та знайдіть індекс цього елемента у відсортованому списку. Також запишіть індекс елемента. 3- Переконайтесь, що в елементі немає дублікатів, якщо він є, то вам потрібно змінити значення вашого індексу на -1. У той час, коли в моїй програмі відбувається саме це. 4- Продовжуйте рахувати інверсію, яка буде вашим значенням індексу, і видаліть елемент, як тільки ви обчислили його інверсію.

def binarySearch(alist, item):
    first = 0
    last = len(alist) - 1
    found = False

    while first <= last and not found:
        midpoint = (first + last)//2
        if alist[midpoint] == item:
            return midpoint
        else:
            if item < alist[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1

def solution(A):

    B = list(A)
    B.sort()
    inversion_count = 0
    for i in range(len(A)):
        j = binarySearch(B, A[i])
        while B[j] == B[j - 1]:
            if j < 1:
                break
            j -= 1

        inversion_count += j
        B.pop(j)

    if inversion_count > 1000000000:
        return -1
    else:
        return inversion_count

print solution([4, 10, 11, 1, 3, 9, 10])

Я опублікував відповідь , яка timeitзіставляє всі відповіді Python на це питання, тому він включає ваш код. Можливо, вам буде цікаво переглянути результати часу.
PM 2Ring

1

Ну, у мене інше рішення, але я боюся, що він би працював лише для різних елементів масиву.

//Code
#include <bits/stdc++.h>
using namespace std;

int main()
{
    int i,n;
    cin >> n;
    int arr[n],inv[n];
    for(i=0;i<n;i++){
        cin >> arr[i];
    }
    vector<int> v;
    v.push_back(arr[n-1]);
    inv[n-1]=0;
    for(i=n-2;i>=0;i--){
        auto it = lower_bound(v.begin(),v.end(),arr[i]); 
        //calculating least element in vector v which is greater than arr[i]
        inv[i]=it-v.begin();
        //calculating distance from starting of vector
        v.insert(it,arr[i]);
        //inserting that element into vector v
    }
    for(i=0;i<n;i++){
        cout << inv[i] << " ";
    }
    cout << endl;
    return 0;
}

Для пояснення мого коду ми продовжуємо додавати елементи з кінця масиву. Для будь-якого вхідного елемента масиву ми знаходимо індекс першого елемента у векторі v, який більший за наш вхідний елемент, і присвоюємо це значення рахунку інверсії індексу вхідного елемента Після цього ми вставляємо цей елемент у вектор v у правильному положенні, таким чином, щоб вектор v залишався у відсортованому порядку.

//INPUT     
4
2 1 4 3

//OUTPUT    
1 0 1 0

//To calculate total inversion count just add up all the elements in output array

1

Ще одне рішення Python, коротке. Використовує вбудований бісект-модуль, який надає функції для вставки елемента на його місце в відсортованому масиві та пошуку індексу елемента в відсортованому масиві.

Ідея полягає в тому, щоб в такому масиві зберігати ліві елементи від n-го, що дозволило б нам легко знайти їх кількість більше n-го.

import bisect
def solution(A):
    sorted_left = []
    res = 0
    for i in xrange(1, len(A)):
        bisect.insort_left(sorted_left, A[i-1])
        # i is also the length of sorted_left
        res += (i - bisect.bisect(sorted_left, A[i]))
    return res

1
Я опублікував відповідь , яка timeitзіставляє всі відповіді Python на це питання, тому він включає ваш код. Можливо, вам буде цікаво переглянути результати часу. : D
PM 2Ring

0

Проста відповідь O (n ^ 2) полягає у використанні вкладених для-циклів та збільшення лічильника для кожної інверсії

int counter = 0;

for(int i = 0; i < n - 1; i++)
{
    for(int j = i+1; j < n; j++)
    {
        if( A[i] > A[j] )
        {
            counter++;
        }
    }
}

return counter;

Тепер я припускаю, що ви хочете більш ефективного рішення, я подумаю про це.


3
Для питань домашнього завдання найкраще дати корисні пропозиції, а не фактичне рішення. Навчіть чоловіка ловити рибу.
Доктор Джонс

3
Це очевидне рішення, яке отримає кожен другий студент першим, я вважаю, що їхній вчитель хоче кращого впровадження, що отримає більше балів.
mbillard

Не обов'язково, це залежить від рівня курсу програмування. Це не так просто для початківця.
Доктор Джонс

вони, швидше за все, хочуть рішення n * log (n)
адереш

0

Одне можливе рішення в C ++, що задовольняє вимогу складності часу O (N * log (N)), полягає в наступному.

#include <algorithm>

vector<int> merge(vector<int>left, vector<int>right, int &counter)
{

    vector<int> result;

    vector<int>::iterator it_l=left.begin();
    vector<int>::iterator it_r=right.begin();

    int index_left=0;

    while(it_l!=left.end() || it_r!=right.end())
    {

        // the following is true if we are finished with the left vector 
        // OR if the value in the right vector is the smaller one.

        if(it_l==left.end() || (it_r!=right.end() && *it_r<*it_l) )
        {
            result.push_back(*it_r);
            it_r++;

            // increase inversion counter
            counter+=left.size()-index_left;
        }
        else
        {
            result.push_back(*it_l);
            it_l++;
            index_left++;

        }
    }

    return result;
}

vector<int> merge_sort_and_count(vector<int> A, int &counter)
{

    int N=A.size();
    if(N==1)return A;

    vector<int> left(A.begin(),A.begin()+N/2);
    vector<int> right(A.begin()+N/2,A.end());

    left=merge_sort_and_count(left,counter);
    right=merge_sort_and_count(right,counter);


    return merge(left, right, counter);

}

Він відрізняється від звичайного сортування злиття лише лічильником.


Це виглядає майже так само, як рішення Java та Python, розміщені раніше, здавалося б, використовуючи один і той же алгоритм, і, таким чином, я не відчуваю, що це додає великої цінності поза ними.
Бернхард Баркер

0

Ось моє O (n log n) рішення в Ruby:

def solution(t)
    sorted, inversion_count = sort_inversion_count(t)
    return inversion_count
end

def sort_inversion_count(t)
    midpoint = t.length / 2
    left_half = t[0...midpoint]
    right_half = t[midpoint..t.length]

    if midpoint == 0
        return t, 0
    end

    sorted_left_half, left_half_inversion_count = sort_inversion_count(left_half)
    sorted_right_half, right_half_inversion_count = sort_inversion_count(right_half)

    sorted = []
    inversion_count = 0
    while sorted_left_half.length > 0 or sorted_right_half.length > 0
        if sorted_left_half.empty?
            sorted.push sorted_right_half.shift
        elsif sorted_right_half.empty?
            sorted.push sorted_left_half.shift
        else
            if sorted_left_half[0] > sorted_right_half[0]
                inversion_count += sorted_left_half.length
                sorted.push sorted_right_half.shift
            else
                sorted.push sorted_left_half.shift
            end
        end
    end

    return sorted, inversion_count + left_half_inversion_count + right_half_inversion_count
end

І кілька тестових випадків:

require "minitest/autorun"

class TestCodility < Minitest::Test
    def test_given_example
        a = [-1, 6, 3, 4, 7, 4]
        assert_equal solution(a), 4
    end

    def test_empty
        a = []
        assert_equal solution(a), 0
    end

    def test_singleton
        a = [0]
        assert_equal solution(a), 0
    end

    def test_none
        a = [1,2,3,4,5,6,7]
        assert_equal solution(a), 0
    end

    def test_all
        a = [5,4,3,2,1]
        assert_equal solution(a), 10
    end

    def test_clones
        a = [4,4,4,4,4,4]
        assert_equal solution(a), 0
    end
end

0

Найкращим оптимізованим способом буде вирішити це шляхом сортування об'єднань, де, зробивши злиття, ми зможемо перевірити, скільки потрібних інверсій, порівнявши лівий і правий масив. Кожного разу, коли елемент у лівому масиві більший, ніж елемент у правому масиві, це буде інверсією.

Підхід до сортування об'єднань: -

Ось код. Код точно такий же, як сортування злиття, за винятком фрагмента коду за mergeToParentметодом, де я підраховую інверсію за інших умов(left[leftunPicked] < right[rightunPicked])

public class TestInversionThruMergeSort {
    
    static int count =0;

    public static void main(String[] args) {
        int[] arr = {6, 9, 1, 14, 8, 12, 3, 2};
        

        partition(arr);

        for (int i = 0; i < arr.length; i++) {

            System.out.println(arr[i]);
        }
        
        System.out.println("inversions are "+count);

    }

    public static void partition(int[] arr) {

        if (arr.length > 1) {

            int mid = (arr.length) / 2;
            int[] left = null;

            if (mid > 0) {
                left = new int[mid];

                for (int i = 0; i < mid; i++) {
                    left[i] = arr[i];
                }
            }

            int[] right = new int[arr.length - left.length];

            if ((arr.length - left.length) > 0) {
                int j = 0;
                for (int i = mid; i < arr.length; i++) {
                    right[j] = arr[i];
                    ++j;
                }
            }

            partition(left);
            partition(right);
            mergeToParent(left, right, arr);
        }

    }

    public static void mergeToParent(int[] left, int[] right, int[] parent) {

        int leftunPicked = 0;
        int rightunPicked = 0;
        int parentIndex = -1;

        while (rightunPicked < right.length && leftunPicked < left.length) {

            if (left[leftunPicked] < right[rightunPicked]) {
                parent[++parentIndex] = left[leftunPicked];
                ++leftunPicked;

            } else {
                count = count + left.length-leftunPicked;
                if ((rightunPicked < right.length)) {
                    parent[++parentIndex] = right[rightunPicked];
                    ++rightunPicked;
                }
            }

        }

        while (leftunPicked < left.length) {
            parent[++parentIndex] = left[leftunPicked];
            ++leftunPicked;
        }

        while (rightunPicked < right.length) {
            parent[++parentIndex] = right[rightunPicked];
            ++rightunPicked;
        }

    }

}

Ще один підхід, де ми можемо порівняти вхідний масив із відсортованим масивом: - Ця реалізація відповіді Diablo. Хоча цьому не слід надавати перевагу підходу, оскільки видалення n елементів із масиву чи списку - це log (n ^ 2).

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;


public class TestInversion {

    public static void main(String[] args) {
        
        Integer [] arr1 = {6, 9, 1, 14, 8, 12, 3, 2};
        
        List<Integer> arr = new ArrayList(Arrays.asList(arr1));
        List<Integer> sortArr = new ArrayList<Integer>();
        
        for(int i=0;i<arr.size();i++){
            sortArr.add(arr.get(i));
         
        }
        
            
        Collections.sort(sortArr);
        
        int inversion = 0;
        
        Iterator<Integer> iter = arr.iterator();
        
        while(iter.hasNext()){
            
            Integer el = (Integer)iter.next();
            int index = sortArr.indexOf(el);
            
            if(index+1 > 1){
                inversion = inversion + ((index+1)-1);
            }
            
            //iter.remove();
            sortArr.remove(el);
            
        }
        
        System.out.println("Inversions are "+inversion);
        
        
        

    }


}

0

Максимальна кількість можливих інверсій для списку розмірів nможе бути узагальнена виразом:

maxPossibleInversions = (n * (n-1) ) / 2

Отже, для масиву розмірів 6максимально можливі інверсії будуть рівні 15.

Для досягнення складності n logn ми могли б повернути алгоритм інверсії на сортування об'єднань.

Ось узагальнені кроки:

  • Розділіть масив на дві частини
  • Зателефонуйте в процедуру mergeSort. Якщо елемент у лівому підмасиві більший, ніж елемент у правому підмножиніinversionCount += leftSubArray.length

Це воно!

Це простий приклад, який я зробив за допомогою Javascript:

var arr = [6,5,4,3,2,1]; // Sample input array

var inversionCount = 0;

function mergeSort(arr) {
    if(arr.length == 1)
        return arr;

    if(arr.length > 1) {
        let breakpoint = Math.ceil((arr.length/2));
        // Left list starts with 0, breakpoint-1
        let leftList = arr.slice(0,breakpoint);
        // Right list starts with breakpoint, length-1
        let rightList = arr.slice(breakpoint,arr.length);

        // Make a recursive call
        leftList = mergeSort(leftList);
        rightList = mergeSort(rightList);

        var a = merge(leftList,rightList);
        return a;
    }
}

function merge(leftList,rightList) {
    let result = [];
    while(leftList.length && rightList.length) {
        /**
         * The shift() method removes the first element from an array
         * and returns that element. This method changes the length
         * of the array.
         */
        if(leftList[0] <= rightList[0]) {
            result.push(leftList.shift());
        }else{
            inversionCount += leftList.length;
            result.push(rightList.shift());
        }
    }

    while(leftList.length)
        result.push(leftList.shift());

    while(rightList.length)
        result.push(rightList.shift());

    console.log(result);
    return result;
}

mergeSort(arr);
console.log('Number of inversions: ' + inversionCount);

0

Реалізація підрахунку інверсій в масиві з сортуванням злиття в Swift:

Зауважте, що кількість свопів збільшується на

nSwaps += mid + 1 - iL 

(яка відносна довжина лівої частини масиву мінус індекс поточного елемента в лівій частині)

... тому що це кількість елементів, які елемент у правій частині масиву повинен був пропустити (# інверсій) для сортування.

func merge(arr: inout [Int], arr2: inout [Int], low: Int, mid: Int, high: Int) -> Int {
    var nSwaps = 0;

    var i = low;
    var iL = low;
    var iR = mid + 1;

    while iL <= mid && iR <= high {
        if arr2[iL] <= arr2[iR] {
            arr[i] = arr2[iL]
            iL += 1
            i += 1
        } else {
            arr[i] = arr2[iR]
            nSwaps += mid + 1 - iL
            iR += 1
            i += 1
        }
    }

    while iL <= mid {
        arr[i] = arr2[iL]
        iL += 1
        i += 1
    }

    while iR <= high {
        arr[i] = arr2[iR]
        iR += 1
        i += 1
    }

    return nSwaps
}

func mergeSort(arr: inout [Int]) -> Int {
    var arr2 = arr
    let nSwaps = mergeSort(arr: &arr, arr2: &arr2, low: 0, high: arr.count-1)
    return nSwaps
}

func mergeSort(arr: inout [Int], arr2: inout [Int], low: Int, high: Int) -> Int {

    if low >= high {
        return 0
    }

    let mid = low + ((high - low) / 2)

    var nSwaps = 0;
    nSwaps += mergeSort(arr: &arr2, arr2: &arr, low: low, high: mid)
    nSwaps += mergeSort(arr: &arr2, arr2: &arr, low: mid+1, high: high)
    nSwaps += merge(arr: &arr, arr2: &arr2, low: low, mid: mid, high: high)

    return nSwaps
}

var arrayToSort: [Int] = [2, 1, 3, 1, 2]
let nSwaps = mergeSort(arr: &arrayToSort)

print(arrayToSort) // [1, 1, 2, 2, 3]
print(nSwaps) // 4

0

Більшість відповідей засновані, MergeSortале це не єдиний спосіб вирішити цеO(nlogn)

Я обговорю кілька підходів.

  1. Використовуйте a Balanced Binary Search Tree

    • Розширіть дерево, щоб зберігати частоти для дублюючих елементів.
    • Ідея полягає в тому, щоб продовжувати підраховувати більші вузли, коли дерево проходить від кореня до листа для вставки.

Щось на зразок цього.

Node *insert(Node* root, int data, int& count){
    if(!root) return new Node(data);
    if(root->data == data){
        root->freq++;
        count += getSize(root->right);
    }
    else if(root->data > data){
        count += getSize(root->right) + root->freq;
        root->left = insert(root->left, data, count);
    }
    else root->right = insert(root->right, data, count);
    return balance(root);
}

int getCount(int *a, int n){
    int c = 0;
    Node *root = NULL;
    for(auto i=0; i<n; i++) root = insert(root, a[i], c);
    return c;
}
  1. Використовуйте a Binary Indexed Tree
    • Створіть суму BIT.
    • Зверніть петлю з кінця і починайте знаходити кількість більших елементів.
int getInversions(int[] a) {
    int n = a.length, inversions = 0;
    int[] bit = new int[n+1];
    compress(a);
    BIT b = new BIT();
    for (int i=n-1; i>=0; i--) {
         inversions += b.getSum(bit, a[i] - 1);
         b.update(bit, n, a[i], 1);
     }
     return inversions;
}
  1. Використовуйте a Segment Tree
    • Створіть дерево підсумовування.
    • Цикл з кінця масиву та запит між [0, a[i]-1]та оновленнямa[i] with 1
int getInversions(int *a, int n) {
    int N = n + 1, c = 0;
    compress(a, n);
    int tree[N<<1] = {0};
    for (int i=n-1; i>=0; i--) {
        c+= query(tree, N, 0, a[i] - 1);
        update(tree, N, a[i], 1);
    }
    return c;
}

Також при використанні BITабо Segment-Treeхорошій ідеї це зробитиCoordinate compression

void compress(int *a, int n) {
    int temp[n];
    for (int i=0; i<n; i++) temp[i] = a[i];
    sort(temp, temp+n);
    for (int i=0; i<n; i++) a[i] = lower_bound(temp, temp+n, a[i]) - temp + 1;
}

0

C ++ Θ (n lg n) Розв’язання з друком пари, що входять до складу інверсії.

int merge(vector<int>&nums , int low , int mid , int high){
    int size1 = mid - low +1;
    int size2= high - mid;
    vector<int>left;
    vector<int>right;
    for(int i = 0  ; i < size1 ; ++i){
        left.push_back(nums[low+i]);
    }
    for(int i = 0 ; i <size2 ; ++i){
        right.push_back(nums[mid+i+1]);
    }
    left.push_back(INT_MAX);
    right.push_back(INT_MAX);
    int i = 0 ;
    int j = 0;
    int start  = low;
    int inversion = 0 ;
    while(i < size1 && j < size2){
        if(left[i]<right[j]){
            nums[start] = left[i];
            start++;
            i++;
        }else{
            for(int l = i ; l < size1; ++l){
                cout<<"("<<left[l]<<","<<right[j]<<")"<<endl;
            }
            inversion += size1 - i;
            nums[start] = right[j];
            start++;
            j++;
        }
    }
    if(i == size1){
        for(int c = j ; c< size2 ; ++c){
            nums[start] = right[c];
            start++;
        }
    }
    if(j == size2){
        for(int c =  i ; c< size1 ; ++c){
            nums[start] = left[c];
            start++;
        }
    }
    return inversion;
}
int inversion_count(vector<int>& nums , int low , int high){
    if(high>low){
        int mid = low + (high-low)/2;
        int left = inversion_count(nums,low,mid);
        int right = inversion_count(nums,mid+1,high);
        int inversion = merge(nums,low,mid,high) + left + right;
        return inversion;
    }
    return 0 ;
}

-1

Використовуйте mergesort у лічильнику кроків злиття, якщо число, скопійоване для виведення, з правого масиву.


Збільшення лічильника (імовірно, на один) для кожного елемента дасть вам занадто мало інверсій.
Бернхард Баркер

-1

Нещодавно мені довелося це робити в R:

inversionNumber <- function(x){
    mergeSort <- function(x){
        if(length(x) == 1){
            inv <- 0
        } else {
            n <- length(x)
            n1 <- ceiling(n/2)
            n2 <- n-n1
            y1 <- mergeSort(x[1:n1])
            y2 <- mergeSort(x[n1+1:n2])
            inv <- y1$inversions + y2$inversions
            x1 <- y1$sortedVector
            x2 <- y2$sortedVector
            i1 <- 1
            i2 <- 1
            while(i1+i2 <= n1+n2+1){
                if(i2 > n2 || i1 <= n1 && x1[i1] <= x2[i2]){
                    x[i1+i2-1] <- x1[i1]
                    i1 <- i1 + 1
                } else {
                    inv <- inv + n1 + 1 - i1
                    x[i1+i2-1] <- x2[i2]
                    i2 <- i2 + 1
                }
            }
        }
        return (list(inversions=inv,sortedVector=x))
    }
    r <- mergeSort(x)
    return (r$inversions)
}

1
@Dukeling Що спонукало вас відкликати свій коментар, але не ваш відгук?
Помітно
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.