Day 20 - Counting inversions


Definition(s)

Inversion Count for an array indicates – how far (or close) the array is from being sorted. If array is already sorted then inversion count is 0. If array is sorted in reverse order that inversion count is the maximum.

Formally speaking, two elements a[i] and a[j] form an inversion if a[i] > a[j] and i < j

Algorithm(s)


In [1]:
from collections import defaultdict
import numpy as np

In [2]:
class FenwickTree:
    def __init__(self, n):
        self._tree = defaultdict(lambda: 0)
        self._n = n

    @staticmethod
    def lsb(x):
        return x & (-x)

    def update(self, position, key):
        while position <= self._n:
            self._tree[position] += key
            position += FenwickTree.lsb(position)

    def query(self, position):
        suma = 0
        while position > 0:
            suma += self._tree[position]
            position -= FenwickTree.lsb(position)

        return suma

In [3]:
def count_inversions(xs, max_size=2**31):
    aib = FenwickTree(max_size)
    inversions = 0

    for i, x in enumerate(xs):
        inversions += i - aib.query(x)
        aib.update(x, 1)

    return inversions

Run(s)


In [4]:
count_inversions([3, 2, 1])


Out[4]:
3

In [5]:
count_inversions(range(10, 0, -1))


Out[5]:
45

In [6]:
count_inversions([3, 4, 1, 2, 5])


Out[6]:
4

In [7]:
xs = np.random.randint(1, 100 + 1, size=20)
print(xs)
count_inversions(xs)


[66  4 69 13 60  2 58 46 63 44 47 65 79 34 38 61 87  3 64  1]
Out[7]:
97

In [8]:
xs = np.random.randint(1, 100 + 1, size=50)
print(xs)
count_inversions(xs)


[71 21 37  4 30 71 33 94 76  8 97 71 92 94 67 15 48 23 37 34 32 26 53 45 74
 89 77 66  9 51 41  6 80 10  2 48  9 80 83 19 16 92 54 90 71 16  7 49 98 76]
Out[8]:
592

In [9]:
count_inversions(np.random.randint(1, 1000, size=1000000), max_size=1000)


Out[9]:
249922755030