Liczenie inwersji w tablicy


108

Projektuję algorytm, aby wykonać następujące czynności: Biorąc pod uwagę tablicę A[1... n], dla każdego i < jznajdź wszystkie pary inwersji takie, że A[i] > A[j]. Używam sortowania przez scalanie i kopiowania tablicy A do tablicy B, a następnie porównuję dwie tablice, ale trudno mi zobaczyć, jak mogę to wykorzystać do znalezienia liczby inwersji. Wszelkie wskazówki lub pomoc będą bardzo mile widziane.

Odpowiedzi:


139

Oto rozwiązanie O (n log n) w Javie.

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);
}

To prawie normalne sortowanie przez scalanie, cała magia jest ukryta w funkcji scalania. Zauważ, że podczas sortowania algorytm usuń inwersje. Podczas scalania algorytm zlicza liczbę usuniętych inwersji (można powiedzieć, że są uporządkowane).

Jedynym momentem, w którym usuwa się inwersje, jest to, że algorytm pobiera element z prawej strony tablicy i łączy go z tablicą główną. Liczba inwersji usuniętych przez tę operację to liczba elementów pozostałych do scalenia z lewej tablicy. :)

Mam nadzieję, że to wystarczająco wyjaśniające.


2
Próbowałem to uruchomić i nie otrzymałem poprawnej odpowiedzi. Czy powinieneś wywołać invCount (intArray) w main, aby rozpocząć? Z intArray będącą nieposortowaną tablicą int? Uruchomiłem go z tablicą wielu liczb całkowitych i otrzymałem -1887062008 jako odpowiedź. Co ja robię źle?
Nearpoint

4
+1, Zobacz podobne rozwiązanie w C ++ 11 , w tym ogólne rozwiązanie oparte na iteratorze i przykładowe testy losowe przy użyciu sekwencji z 5-25 elementów. Cieszyć się!.
WhozCraig

3
To nie jest rozwiązanie. Próbowałem go uruchomić i daje nieprawidłowe wyniki.
mirgee

2
Przepraszam za nowatorskie pytanie, ale o co chodzi z dodawaniem left.length - ido licznika inwersji? Sądzę, że sensowne byłoby dodanie 1, ponieważ wpadłeś w logiczny przypadek, w którym porównanie między dwiema podtablicami ma większy lewy element tablicy niż prawy. Ktoś może mi to wyjaśnić, jakbym miał 5 lat?
Alfredo Gallegos,

2
@AlfredoGallegos, krótka ilustracja odpowiedzi Marka. Rozważ dwie tablice: [6, 8] i [4, 5]. Kiedy widzisz, że 6 jest większe niż 4, bierzesz 4 i umieszczasz w arr. Ale to nie jest jedna inwersja. Znalazłeś inwersje dla wszystkich elementów w lewej tablicy, które są większe niż 6. W naszym przypadku zawiera również 8. Więc dodaje się 2 count, co jest równe left.length - i.
ilya

86

Znalazłem to w czasie O (n * log n) w następujący sposób.

  1. Scal tablicę sortowania A i utwórz kopię (tablica B)
  2. Weź A [1] i znajdź jego pozycję w posortowanej tablicy B za pomocą wyszukiwania binarnego. Liczba inwersji tego elementu będzie o jeden mniejsza niż numer indeksu jego pozycji w B, ponieważ każda mniejsza liczba pojawiająca się po pierwszym elemencie A będzie inwersją.

    2a. akumulują liczbę inwersji, aby zliczać zmienną num_inversions.

    2b. usuń A [1] z tablicy A, a także z odpowiadającej jej pozycji w tablicy B

  3. uruchom ponownie od kroku 2, aż nie będzie już elementów w A.

Oto przykład uruchomienia tego algorytmu. Oryginalna tablica A = (6, 9, 1, 14, 8, 12, 3, 2)

1: Scal sortowanie i kopiuj do tablicy B.

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

2: Weź A [1] i wyszukaj binarne, aby znaleźć go w tablicy B

A [1] = 6

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

6 znajduje się na czwartej pozycji tablicy B, więc są 3 inwersje. Wiemy o tym, ponieważ 6 znajdowało się na pierwszej pozycji w tablicy A, więc każdy element o niższej wartości, który później pojawia się w tablicy A, miałby indeks j> i (ponieważ i w tym przypadku wynosi 1).

2.b: Usuń A [1] z tablicy A, a także z odpowiadającej jej pozycji w tablicy B (elementy pogrubione są usuwane).

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: Uruchom ponownie od kroku 2 na nowych macierzach A i B.

A [1] = 9

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

9 znajduje się teraz na piątej pozycji tablicy B, więc są 4 inwersje. Wiemy o tym, ponieważ 9 znajdowało się na pierwszej pozycji w tablicy A, więc każdy element o niższej wartości, który się później pojawi, miałby indeks j> i (ponieważ i w tym przypadku jest znowu 1). Usuń A [1] z tablicy A, a także z odpowiadającej jej pozycji w tablicy B (elementy pogrubione są usuwane)

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

Kontynuując ten wątek, uzyskamy całkowitą liczbę inwersji dla tablicy A po zakończeniu pętli.

Krok 1 (sortowanie przez scalanie) wymagałoby wykonania O (n * log n). Krok 2 byłby wykonywany n razy i przy każdym wykonaniu wykonywałoby wyszukiwanie binarne, które wymaga O (log n) do uruchomienia w sumie O (n * log n). Całkowity czas pracy wyniósłby zatem O (n * log n) + O (n * log n) = O (n * log n).

Dzięki za pomoc. Zapisanie przykładowych tablic na kartce papieru naprawdę pomogło w wizualizacji problemu.


1
dlaczego używać sortowania przez scalanie, a nie sortowania szybkiego?
Alcott,

5
@Alcott Szybkie sortowanie ma najgorszy czas działania wynoszący O (n ^ 2), gdy lista jest już posortowana i co rundę wybiera się pierwszy przestaw. Najgorszym przypadkiem sortowania przez scalanie jest O (n log n)
user482594,

29
Krok usuwania ze standardowej tablicy powoduje, że twój algorytm O (n ^ 2), z powodu przesunięcia wartości. (Dlatego sortowanie przez wstawianie to O (n ^ 2))
Kyle Butt

rozpoczęcie od pierwszego elementu tablicy B i policzenie elementów znajdujących się przed nim w tablicy A również dałoby ten sam wynik, pod warunkiem, że wyeliminujesz je w sposób opisany w odpowiedzi.
tutak

@el diablo Jak usunąć elementy, aby uniknąć złożoności n ^ 2?
Jerky,

26

W Pythonie

# 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
Jestem zdumiony tym, jak udało mi się to osiągnąć +13 - nie jestem szczególnie biegły w Pythonie, ale wydaje się, że jest prawie taki sam, jak wersja Java zaprezentowana 2 lata wcześniej , z tym wyjątkiem, że nie daje to żadnego wyjaśnienia . Publikowanie odpowiedzi w każdym innym języku jest aktywnie szkodliwe dla IMO - prawdopodobnie są tysiące, jeśli nie dużo więcej języków - mam nadzieję, że nikt nie będzie twierdził, że powinniśmy publikować tysiące odpowiedzi na pytanie - Stack Exchange nie został do tego stworzony .
Bernhard Barker

1
@tennenrishin OK, może nie tysiące. Ale gdzie wyznaczamy granicę? Obecnie, jak liczę, jest dziesięć odpowiedzi podających już to samo podejście . To około 43% odpowiedzi (z wyłączeniem braku odpowiedzi) - sporo miejsca na zajęcie, biorąc pod uwagę, że przedstawiono tutaj pół tuzina innych podejść. Nawet jeśli istnieją tylko 2 odpowiedzi dla tego samego podejścia, to nadal niepotrzebnie osłabia odpowiedzi. I przedstawiłem całkiem przyzwoity argument za tą odpowiedzią, która nie była przydatna w moim poprzednim komentarzu.
Bernhard Barker

3
@Dukeling Podobnie jak ty, ja nie znam Pythona i bardziej znam Javę. Uważam, że to rozwiązanie jest znacznie mniej czytelne niż rozwiązanie Java. Jest więc rozsądne, że dla niektórych osób odwrotna sytuacja może być prawdą w tym samym stopniu.
Rozmyślny

3
Dla zdecydowanej większości użytkowników Python jest bliski kodowi sudo. Szczerze mówiąc uważam, że jest to znacznie bardziej czytelne niż wersja java, mimo że nie ma wyjaśnienia. Nie widzę powodu, aby się tak denerwować, jeśli niektórym czytelnikom pomaga to.
Francisco Vargas

2
To rozwiązanie jest bardzo dobre i czytelne dla użytkowników Pythona. Ludzie chcą zobaczyć, jak inni zaimplementowali to w Pythonie.
Aerin

24

Zastanawiam się, dlaczego nikt jeszcze nie wspomniał o drzewach indeksowanych binarnie . Możesz użyć jednego, aby zachować sumy przedrostków na wartościach elementów permutacji. Następnie możesz po prostu przejść od prawej do lewej i policzyć dla każdego elementu liczbę elementów mniejszą niż po prawej:

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

Złożoność wynosi O (n log n), a stały współczynnik jest bardzo niski.


chyba najlepsze podejście :)
Nilutpal Borgohain

@NilutpalBorgohain Thanks :) Wygląda na to, że wymaga przynajmniej najmniejszej liczby kodów spośród kandydatów O (n log n).
Niklas B.

1
Dzięki za to. Jakie jest znaczenie tej i -= i & -ilinii? I podobniei += i & -i
Gerard Condon,

1
@GerardCondon to w zasadzie struktura danych BIT. Link wyjaśniający to można znaleźć w odpowiedzi
Niklas B.

TIL o drzewach Fenwick. Dzięki! Opublikowałem odpowiedź , która timeitporównuje wszystkie odpowiedzi Pythona na to pytanie, więc zawiera Twój kod. Możesz być zainteresowany spojrzeniem na wyniki synchronizacji.
PM 2Ring

14

Miałem podobne pytanie do pracy domowej. Ograniczono mnie, że musi mieć wydajność O (nlogn).

Skorzystałem z zaproponowanego przez ciebie pomysłu użycia Mergesort, ponieważ ma on już właściwą wydajność. Właśnie wstawiłem kod do funkcji scalającej, która w zasadzie brzmiała: Za każdym razem, gdy liczba z tablicy po prawej stronie jest dodawana do tablicy wyjściowej, dodaję do całkowitej liczby inwersji liczbę liczb pozostałych w lewej tablicy.

Ma to dla mnie dużo sensu teraz, kiedy wystarczająco o tym myślałem. Twoje liczenie, ile razy jest większa liczba poprzedzająca jakiekolwiek liczby.

hth.


6
i wspierać swoją odpowiedź, zasadnicza różnica od seryjnej sortowania jest funkcja scalania gdy element 2P tablicy zostanie skopiowany do tablicy wyjściowej => Licznik inwersja przez przyrost liczby elementów pozostających w 1L tablicy
Alex.Salnikov

11

Podstawowym celem tej odpowiedzi jest porównanie prędkości różnych wersji języka Python znalezionych tutaj, ale mam też kilka własnych uwag. (FWIW, właśnie odkryłem to pytanie podczas powtórnego wyszukiwania).

Względne szybkości wykonywania algorytmów zaimplementowanych w CPythonie mogą różnić się od tych, których można by oczekiwać po prostej analizie algorytmów i doświadczeniach z innymi językami. Dzieje się tak, ponieważ Python zapewnia wiele zaawansowanych funkcji i metod zaimplementowanych w C, które mogą działać na listach i innych kolekcjach z prędkością bliską szybkości, jaką można uzyskać w całkowicie skompilowanym języku, więc te operacje działają znacznie szybciej niż równoważne algorytmy zaimplementowane „ręcznie” w Pythonie kod.

Kod korzystający z tych narzędzi może często przewyższać teoretycznie lepsze algorytmy, które próbują zrobić wszystko za pomocą operacji Pythona na poszczególnych elementach kolekcji. Oczywiście ma na to również wpływ faktyczna ilość przetwarzanych danych. Jednak w przypadku średnich ilości danych kod wykorzystujący algorytm O (n²) działający z szybkością C może z łatwością pokonać algorytm O (n log n), który wykonuje większość swojej pracy z pojedynczymi operacjami w języku Python.

Wiele z opublikowanych odpowiedzi na to pytanie polegające na liczeniu inwersji używa algorytmu opartego na łączeniu. Teoretycznie jest to dobre podejście, chyba że rozmiar tablicy jest bardzo mały. Ale wbudowany w Pythona TimSort (hybrydowy stabilny algorytm sortowania, wywodzący się z sortowania przez scalanie i sortowanie przez wstawianie) działa z szybkością C, a scalanie zakodowane ręcznie w Pythonie nie może mieć nadziei na konkurowanie z nim pod względem szybkości.

Jedno z bardziej intrygujących rozwiązań tutaj, w odpowiedzi opublikowanej przez Niklasa B , wykorzystuje wbudowane sortowanie do określenia rankingu elementów tablicy oraz binarne drzewo indeksowane (aka drzewo Fenwicka) do przechowywania skumulowanych sum wymaganych do obliczenia inwersji liczyć. Próbując zrozumieć tę strukturę danych i algorytm Niklasa, napisałem kilka własnych odmian (zamieszczonych poniżej). Ale odkryłem również, że w przypadku umiarkowanych rozmiarów list w rzeczywistości szybciej jest używać wbudowanej sumfunkcji Pythona niż uroczego drzewa Fenwicka.

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

W końcu, gdy rozmiar listy osiągnie około 500, aspekt O (n²) wywołania sumwewnątrz tej forpętli powraca do góry nogami i wydajność zaczyna spadać.

Mergesort nie jest jedynym sortowaniem O (nlogn), a kilka innych można wykorzystać do wykonywania zliczania inwersji. Odpowiedź prasadvka używa binarnego sortowania drzewa, jednak jego kod wydaje się być w C ++ lub jednej z jego pochodnych. Więc dodałem wersję Pythona. Pierwotnie użyłem klasy do implementacji węzłów drzewa, ale odkryłem, że dyktowanie jest zauważalnie szybsze. Ostatecznie użyłem listy, która jest jeszcze szybsza, chociaż sprawia, że ​​kod jest trochę mniej czytelny.

Jedną z zalet treeort jest to, że dużo łatwiej jest wdrożyć iteracyjnie niż scalanie. Python nie optymalizuje rekurencji i ma limit głębokości rekurencji (chociaż można go zwiększyć, jeśli naprawdę tego potrzebujesz). Oczywiście wywołania funkcji w Pythonie są stosunkowo wolne, więc kiedy próbujesz zoptymalizować pod kątem szybkości, dobrze jest unikać wywołań funkcji, jeśli jest to praktyczne.

Kolejny rodzaj O (nlogn) to czcigodny rodzaj radix. Jego dużą zaletą jest to, że nie porównuje kluczy ze sobą. Wadą jest to, że działa najlepiej na ciągłych sekwencjach liczb całkowitych, najlepiej na permutacji liczb całkowitych, range(b**m)gdzie bzwykle jest 2. Dodałem kilka wersji opartych na sortowaniu radix po próbie odczytania zliczania inwersji, liczenia ortogonalnego zakresu offline i powiązanych problemów, czyli związane z obliczaniem liczby „inwersji” w permutacji .

Aby efektywnie używać sortowania radix do liczenia inwersji w ogólnej sekwencji seqo długości n, możemy utworzyć permutację, range(n)która ma taką samą liczbę inwersji jak seq. Możemy to zrobić w (w najgorszym) czasie O (nlogn) za pośrednictwem TimSort. Sztuczka polega na permutowaniu indeksów seqprzez sortowanie seq. Łatwiej to wyjaśnić na małym przykładzie.

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

wynik

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

Sortując pary (wartość, indeks) seq, permutowaliśmy indeksy seqz taką samą liczbą swapów, które są wymagane do ułożenia seqw pierwotnym porządku z sortowanego porządku. Możemy utworzyć tę permutację, sortując range(n)za pomocą odpowiedniej funkcji klucza:

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

wynik

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

Możemy tego uniknąć lambdastosując seq„s .__getitem__metody:

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

Jest to tylko trochę szybsze, ale szukamy wszystkich ulepszeń szybkości, jakie możemy uzyskać. ;)


Poniższy kod przeprowadza timeittesty wszystkich istniejących algorytmów Pythona na tej stronie, a także kilku moich własnych: kilka wersji brutalnej siły O (n²), kilka odmian algorytmu Niklasa B i oczywiście jeden oparty na połączeniu (które pisałem bez odwoływania się do istniejących odpowiedzi). Zawiera również mój kod treeort oparty na liście, z grubsza pochodzący z kodu prasadvk, i różne funkcje oparte na sortowaniu radix, niektóre używają strategii podobnej do podejścia scalania, a niektóre używają sumdrzewa Fenwicka.

Ten program mierzy czas wykonania każdej funkcji na szeregu losowych list liczb całkowitych; może również sprawdzić, czy każda funkcja daje takie same wyniki, jak inne i czy nie modyfikuje listy wejściowej.

Każde timeitwywołanie daje wektor zawierający 3 wyniki, które sortuję. Główną wartością patrzeć tutaj jest jeden minimalny, inne wartości jedynie wskazywać na ile wiarygodne jest to wartość minimalna, jak omówiono w nocie w tych timeitdocs modułowych .

Niestety, wynik tego programu jest zbyt duży, aby uwzględnić go w tej odpowiedzi, więc zamieszczam go w jego własnej odpowiedzi (wiki społeczności) .

Wynik pochodzi z 3 uruchomień na mojej starożytnej 32-bitowej jednordzeniowej maszynie 2GHz z Pythonem 3.6.0 na starej dystrybucji pochodnej Debiana. YMMV. Podczas testów zamknąłem przeglądarkę internetową i rozłączyłem się z routerem, aby zminimalizować wpływ innych zadań na procesor.

Pierwsze uruchomienie testuje wszystkie funkcje o rozmiarach list od 5 do 320, z rozmiarami pętli od 4096 do 64 (gdy rozmiar listy się podwoi, rozmiar pętli zmniejsza się o połowę). Pula losowa użyta do skonstruowania każdej listy jest o połowę mniejsza od samej listy, więc prawdopodobnie otrzymamy wiele duplikatów. Niektóre algorytmy liczenia inwersji są bardziej wrażliwe na duplikaty niż inne.

Drugi przebieg wykorzystuje większe listy: od 640 do 10240 i stałą pętlę o rozmiarze 8. Aby zaoszczędzić czas, eliminuje kilka najwolniejszych funkcji z testów. My brute-force O funkcje (n²) to tylko sposób zbyt powolny w tych rozmiarach, jak wspomniano wcześniej, mój kod, który używa sum, która robi tak dobrze na małych i średnich list, po prostu nie może nadążyć na dużych listach.

Ostatnie uruchomienie obejmuje rozmiary list od 20480 do 655360 i stały rozmiar pętli 4, z 8 najszybszymi funkcjami. W przypadku list o rozmiarach poniżej 40 000 lub więcej kod Tima Babycha jest wyraźnym zwycięzcą. Dobra robota Tim! Kod Niklasa B jest również dobrym wszechstronnym narzędziem, chociaż na mniejszych listach jest pokonany. Kod „python” oparty na bisekcji również radzi sobie całkiem nieźle, chociaż wydaje się być trochę wolniejszy w przypadku ogromnych list z wieloma duplikatami, prawdopodobnie z powodu tej liniowej whilepętli, której używa do przechodzenia przez powtórzenia.

Jednak w przypadku list o bardzo dużych rozmiarach algorytmy oparte na bisekcji nie mogą konkurować z prawdziwymi algorytmami 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

Proszę zobaczyć tutaj wyjście


Dzięki, to było całkiem zabawne :) Wyraźnie widać korzyści płynące z używania modułu C - czyli w połowie.
Tim Babych

Problem w tym, że zwycięzca używa (teoretycznie) algorytmu kwadratowego. dla rozmiaru ~ 100 000 zostanie pokonany przez innych. Zredagowałem mój post, aby umieścić rozwiązanie quasi-liniowe w języku Python.
BM

@BM Jasne, ale podejście Tima do połowy jest całkiem dobre, dopóki nie osiągniesz rozmiaru około 45 000. Mam jeszcze kilka rozwiązań, które dodam tutaj mniej więcej następnego dnia.
PM 2Ring

@TimBabych Czy mówisz, że bisectjest C? Jestem prawie pewien, że to Python.
Stefan Pochmann


10

Liczbę inwersji można znaleźć, analizując proces scalania w sortowaniu przez scalanie: proces scalania

Podczas kopiowania elementu z drugiej tablicy do tablicy merge (9 w tym przykładzie), zachowuje on swoje miejsce względem innych elementów. Podczas kopiowania elementu z pierwszej tablicy do tablicy merge (tutaj 5) jest on odwracany, a wszystkie elementy pozostają w drugiej tablicy (2 inwersje z 3 i 4). Tak więc niewielka modyfikacja sortowania przez scalanie może rozwiązać problem w O (n ln n).
Na przykład, po prostu odkomentuj dwa # wiersze w kodzie Pythona scalonego, aby uzyskać liczbę.

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

EDYCJA 1

To samo zadanie można osiągnąć dzięki stabilnej wersji szybkiego sortowania, znanej jako nieco szybsza:

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)

Wybierając pivot jako ostatni element, inwersje są dobrze zliczane, a czas wykonania o 40% lepszy niż w przypadku scalenia powyższego.

EDYCJA 2

Aby uzyskać wydajność w Pythonie, wersja numpy & numba:

Najpierw część numpy, która używa 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)

I część numba dla wydajnego podejścia 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  

Opublikowałem odpowiedź , która timeitporównuje wszystkie odpowiedzi Pythona na to pytanie, więc zawiera Twój kod. Możesz być zainteresowany spojrzeniem na wyniki synchronizacji.
PM 2Ring

Brak problemów z wydajnością w tym poście ... Spróbuję za jakiś czas. Numpy numba dozwolone;)?
BM

Nigdy nie korzystałem z Numby, ale trochę używałem Numpy i pomyślałem o dodaniu wersji Numpy samodzielnie, ale postanowiłem ograniczyć testy do rozwiązań, które używają tylko standardowej biblioteki. Ale myślę, że byłoby interesujące zobaczyć, jak wypada porównanie rozwiązania Numpy. Podejrzewam, że na małych listach nie będzie szybciej.
PM 2Ring

100-krotne przyspieszenie jest imponujące! Ale nie mogę go uruchomić, ponieważ nie mam Numby. I jak powiedziałem wcześniej, włączenie go do mojej timeitkolekcji byłoby nieuczciwe.
PM 2Ring

8

Zauważ, że odpowiedź Geoffreya Irvinga jest błędna.

Liczba inwersji w tablicy to połowa całkowitej odległości elementów, które należy przesunąć, aby posortować tablicę. Dlatego można go obliczyć, sortując tablicę, zachowując wynikową permutację p [i], a następnie obliczając sumę abs (p [i] -i) / 2. Zajmuje to O (n log n) czasu, który jest optymalny.

Alternatywną metodę podano pod adresem http://mathworld.wolfram.com/PermutationInversion.html . Ta metoda jest równoważna sumie max (0, p [i] -i), która jest równa sumie abs (p [i] -i]) / 2, ponieważ całkowita odległość elementów przesuniętych w lewo jest równa elementy całkowitej odległości przesuwają się w prawo.

Weźmy na przykład sekwencję {3, 2, 1}. Istnieją trzy inwersje: (3, 2), (3, 1), (2, 1), więc liczba inwersji to 3. Jednak zgodnie z cytowaną metodą odpowiedź brzmiałaby 2.


Zamiast tego poprawną odpowiedź można znaleźć, licząc minimalną wymaganą liczbę sąsiednich swapów. Zobacz dyskusję: stackoverflow.com/questions/20990127/…
Isaac Turner


4

Oto jedno możliwe rozwiązanie ze zmiennością drzewa binarnego. Dodaje pole o nazwie rightSubTreeSize do każdego węzła drzewa. Kontynuuj wstawianie liczby do drzewa binarnego w kolejności, w jakiej pojawiają się w tablicy. Jeśli liczba idzie w lewo od węzła, licznik inwersji dla tego elementu wyniesie (1 + rightSubTreeSize). Ponieważ wszystkie te elementy są większe niż bieżący element i pojawiłyby się wcześniej w tablicy. Jeśli element trafi na prawą stronę węzła, po prostu zwiększ jego rightSubTreeSize. Poniżej znajduje się kod.

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;

To ciekawe podejście i wydaje się, że jest dość szybkie. Jednak w if(p->data < q->data)przeciwnym razie porównanie to musi być duplikatem, który nie jest obsługiwany poprawnie. I nie ma potrzeby testowania qna początku pętli, bezwarunkowa whilepętla działa dobrze. Poza tym zapomniałeś wspomnieć, jaki to język. :) I wydaje się, że twoja funkcja utraciła swój nagłówek.
PM 2Ring

Właśnie dodałem wersję Pythona opartą na algorytmie drzewa do mojej odpowiedzi. Oczywiście nie jest tak szybka jak w pełni skompilowana wersja, ale radzi sobie całkiem dobrze w porównaniu z innymi wersjami Pythona.
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
Czy różni się to znacznie od opublikowanych już rozwiązań Java i Python ? Ponadto odpowiedzi dotyczące samego kodu nie są szczególnie dobre w IMO, zwłaszcza biorąc pod uwagę, że to pytanie nie określa nawet języka.
Bernhard Barker

2

Ponieważ jest to stare pytanie, udzielę odpowiedzi w C.

#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, ponieważ odpowiedź w każdym innym języku prowadziłaby do beznadziejnie zbyt wielu odpowiedzi, z których wszystkie zasadniczo powielałyby informacje już przedstawione w innych odpowiedziach. Ponadto jest to zasadniczo odpowiedź zawierająca tylko kod, bez wyjaśnienia, co w najlepszym przypadku jest odpowiednie głównie w przypadku pytań dotyczących tego języka.
Bernhard Barker

2

Oto rozwiązanie 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

Ta odpowiedź zawiera wyniki timeittestów wygenerowanych przez kod w mojej głównej odpowiedzi . Zobacz tę odpowiedź, aby poznać szczegóły!

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

Oto kod C do inwersji zliczania

#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;
}

Szczegółowe wyjaśnienie podano tutaj: http://www.geeksforgeeks.org/counting-inversions/


1

O (n log n) czas, O (n) rozwiązanie przestrzenne w java.

Scalanie z poprawką pozwalającą zachować liczbę inwersji wykonanych podczas etapu scalania. (aby uzyskać dobrze wyjaśnione połączenie, zajrzyj na http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html )

Ponieważ scalanie można wykonać na miejscu, złożoność przestrzeni można poprawić do O (1).

Przy takim sortowaniu inwersje zachodzą tylko w kroku scalania i tylko wtedy, gdy musimy wstawić element drugiej części przed elementami z pierwszej połowy, np.

  • 0 5 10 15

połączony z

  • 1 6 22

mamy 3 + 2 + 0 = 5 inwersji:

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

Po wykonaniu 5 inwersji nasza nowa połączona lista to 0, 1, 5, 6, 10, 15, 22

W witrynie Codility znajduje się zadanie demonstracyjne o nazwie ArrayInversionCount, w którym można przetestować swoje rozwiązanie.

    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

Oto implementacja O (n * log (n)) perla:

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

Moja odpowiedź w Pythonie:

1- Najpierw posortuj tablicę i zrób jej kopię. W moim programie B reprezentuje posortowaną tablicę. 2- Powtórz oryginalną tablicę (nieposortowaną) i znajdź indeks tego elementu na posortowanej liście. Zanotuj również indeks elementu. 3- Upewnij się, że element nie ma żadnych duplikatów, jeśli tak, musisz zmienić wartość swojego indeksu o -1. Warunek while w moim programie dokładnie to robi. 4- Kontynuuj liczenie inwersji, która będzie wartością indeksu i usuń element po obliczeniu jego inwersji.

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

Opublikowałem odpowiedź , która timeitporównuje wszystkie odpowiedzi Pythona na to pytanie, więc zawiera Twój kod. Możesz być zainteresowany spojrzeniem na wyniki synchronizacji.
PM 2Ring

1

Cóż, mam inne rozwiązanie, ale obawiam się, że zadziałaby tylko dla różnych elementów tablicy.

//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;
}

Aby wyjaśnić mój kod, dodajemy elementy od końca tablicy. Dla każdego przychodzącego elementu tablicy znajdujemy indeks pierwszego elementu w wektorze v, który jest większy niż nasz element przychodzący i przypisujemy tę wartość do inwersji liczby indeksu przychodzącego elementu Następnie wstawiamy ten element do wektora v w jego prawidłowej pozycji, tak aby wektor v pozostał w posortowanej kolejności.

//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

Kolejne rozwiązanie Pythona, krótkie. Korzysta z wbudowanego modułu z połówką, który zapewnia funkcje wstawiania elementu w jego miejsce w posortowanej tablicy i znajdowania indeksu elementu w posortowanej tablicy.

Chodzi o to, aby przechowywać elementy po lewej stronie n-tej w takiej tablicy, co pozwoliłoby nam łatwo znaleźć ich liczbę większą niż n-tą.

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
Opublikowałem odpowiedź , która timeitporównuje wszystkie odpowiedzi Pythona na to pytanie, więc zawiera Twój kod. Możesz być zainteresowany spojrzeniem na wyniki synchronizacji. : D
PM 2Ring

0

Prostą odpowiedzią O (n ^ 2) jest użycie zagnieżdżonych pętli for i zwiększanie licznika dla każdej inwersji

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;

Przypuszczam, że chcesz bardziej wydajnego rozwiązania, pomyślę o tym.


3
W przypadku pytań do pracy domowej najlepiej jest podać pomocne sugestie, a nie konkretne rozwiązanie. Naucz człowieka łowić ryby.
Doctor Jones,

3
To oczywiste rozwiązanie, które każdy inny uczeń otrzyma pierwszy, przypuszczam, że ich nauczyciel chce lepszej implementacji, która da im więcej punktów.
mbillard

Niekoniecznie zależy to od poziomu kursu programowania. To nie jest takie proste dla początkującego.
Doctor Jones,

najprawdopodobniej chcą rozwiązania n * log (n)
aderesh

0

Jedno możliwe rozwiązanie w C ++ spełniające wymaganie złożoności czasowej O (N * log (N)) byłoby następujące.

#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);

}

Różni się od zwykłego sortowania przez scalanie tylko licznikiem.


Wydaje się, że jest to prawie to samo, co opublikowane wcześniej rozwiązania Java i Python, które pozornie używają tego samego algorytmu, a zatem nie sądzę, aby dodało to wiele wartości poza nimi.
Bernhard Barker

0

Oto moje rozwiązanie O (n log n) w Rubim:

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

I kilka przypadków testowych:

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

Najlepszym zoptymalizowanym sposobem będzie rozwiązanie tego problemu przez sortowanie przez scalanie, gdzie po scaleniu możemy sprawdzić, ile inwersji jest wymaganych, porównując lewą i prawą tablicę. Ilekroć element po lewej tablicy jest większy niż element po prawej tablicy, będzie to inwersja.

Metoda sortowania przez scalanie: -

Oto kod. Kod jest dokładnie taki sam jak sortowanie przez scalanie, z wyjątkiem fragmentu kodu w mergeToParentmetodzie, w której liczę inwersję w warunkach else(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;
        }

    }

}

Inne podejście, w którym możemy porównać tablicę wejściową z tablicą posortowaną: - Ta implementacja odpowiedzi Diablo. Chociaż nie powinno to być preferowane podejście, ponieważ usunięcie n elementów z tablicy lub listy to 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

Maksymalną liczbę możliwych inwersji dla listy rozmiarów nmożna uogólnić za pomocą wyrażenia:

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

Więc dla tablicy o rozmiarze 6maksymalne możliwe inwersje będą równe 15.

Aby osiągnąć złożoność n logn, moglibyśmy zastosować algorytm inwersji podczas sortowania przez scalanie.

Oto ogólne kroki:

  • Podziel tablicę na dwie części
  • Wywołaj procedurę mergeSort. Jeśli element w lewej podtablicy jest większy niż element w prawej podtablicy makeinversionCount += leftSubArray.length

Otóż ​​to!

To jest prosty przykład, który zrobiłem przy użyciu 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

Implementacja liczenia inwersji w tablicy z sortowaniem przez scalanie w Swift:

Zauważ, że liczba swapów jest zwiększana o

nSwaps += mid + 1 - iL 

(czyli względna długość lewej strony tablicy pomniejszona o indeks bieżącego elementu po lewej stronie)

... ponieważ jest to liczba elementów, które element po prawej stronie tablicy musiał pominąć (liczba inwersji), aby zostać posortowanym.

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

Większość odpowiedzi opiera się na nich, MergeSortale nie jest to jedyny sposób rozwiązania tego problemuO(nlogn)

Omówię kilka podejść.

  1. Użyć Balanced Binary Search Tree

    • Rozszerz swoje drzewo, aby przechowywać częstotliwości dla zduplikowanych elementów.
    • Chodzi o to, aby nadal liczyć większe węzły, gdy drzewo przechodzi od korzenia do liścia w celu wstawienia.

Coś takiego.

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. Użyć Binary Indexed Tree
    • Utwórz sumaryczny BIT.
    • Zrób pętlę od końca i zacznij znajdować liczbę większych elementów.
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. Użyć Segment Tree
    • Utwórz drzewo segmentu sumowania.
    • Pętla od końca tablicy i zapytanie między [0, a[i]-1]i updatea[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;
}

Również podczas używania BITlub Segment-Treedobrym pomysłem jest to zrobić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) Rozwiązanie z wypisaniem pary składającej się na inwersję.

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

Użyj scalania, w kroku scalania incremeant licznika, jeśli liczba kopiowana do wyjścia pochodzi z prawej tablicy.


Zwiększenie licznika (prawdopodobnie o jeden) dla każdego elementu da za mało inwersji.
Bernhard Barker

-1

Ostatnio musiałem to zrobić w 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 Co skłoniło Cię do wycofania komentarza, ale nie do wycofania głosu przeciw?
Rozmyślny
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.