Skip to content

Commit 32abbf6

Browse files
divide and conquer and brute force algorithms for array-inversions counting
1 parent 27205d4 commit 32abbf6

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed

divide_and_conquer/inversions.py

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
from __future__ import print_function, absolute_import, division
2+
3+
"""
4+
Given an array-like data structure A[1..n], how many pairs
5+
(i, j) for all 1 <= i < j <= n such that A[i] > A[j]? These pairs are
6+
called inversions. Counting the number of such inversions in an array-like
7+
object is the important. Among other things, counting inversions can help
8+
us determine how close a given array is to being sorted
9+
10+
In this implementation, I provide two algorithms, a divide-and-conquer
11+
algorithm which runs in nlogn and the brute-force n^2 algorithm.
12+
13+
"""
14+
15+
16+
def count_inversions_bf(arr):
17+
"""
18+
Counts the number of inversions using a a naive brute-force algorithm
19+
20+
Parameters
21+
----------
22+
arr: arr: array-like, the list containing the items for which the number
23+
of inversions is desired. The elements of `arr` must be comparable.
24+
25+
Returns
26+
-------
27+
num_inversions: The total number of inversions in `arr`
28+
29+
"""
30+
31+
num_inversions = 0
32+
n = len(arr)
33+
34+
for i in range(n-1):
35+
for j in range(i + 1, n):
36+
if arr[i] > arr[j]:
37+
num_inversions += 1
38+
39+
return num_inversions
40+
41+
42+
def count_inversions_recursive(arr):
43+
"""
44+
Counts the number of inversions using a divide-and-conquer algorithm
45+
46+
Parameters
47+
-----------
48+
arr: array-like, the list containing the items for which the number
49+
of inversions is desired. The elements of `arr` must be comparable.
50+
51+
Returns
52+
-------
53+
C: a sorted copy of `arr`.
54+
num_inversions: int, the total number of inversions in 'arr'
55+
56+
"""
57+
if len(arr) <= 1:
58+
return arr, 0
59+
else:
60+
mid = len(arr)//2
61+
P = arr[0:mid]
62+
Q = arr[mid:]
63+
64+
A, inversion_p = count_inversions_recursive(P)
65+
B, inversions_q = count_inversions_recursive(Q)
66+
C, cross_inversions = _count_cross_inversions(A, B)
67+
68+
num_inversions = inversion_p + inversions_q + cross_inversions
69+
return C, num_inversions
70+
71+
72+
def _count_cross_inversions(P, Q):
73+
"""
74+
Counts the inversions across two sorted arrays.
75+
And combine the two arrays into one sorted array
76+
77+
For all 1<= i<=len(P) and for all 1 <= j <= len(Q),
78+
if P[i] > Q[j], then (i, j) is a cross inversion
79+
80+
Parameters
81+
----------
82+
P: array-like, sorted in non-decreasing order
83+
Q: array-like, sorted in non-decreasing order
84+
85+
Returns
86+
------
87+
R: array-like, a sorted array of the elements of `P` and `Q`
88+
num_inversion: int, the number of inversions across `P` and `Q`
89+
90+
"""
91+
92+
R = []
93+
i = j = num_inversion = 0
94+
while i < len(P) and j < len(Q):
95+
if P[i] > Q[j]:
96+
# if P[1] is > Q[j], then P[k] for all k > i, P[K] > Q[j]
97+
# and therefore are inversions. This claim emerges from the
98+
# property that P is sorted.
99+
num_inversion += (len(P) - i)
100+
R.append(Q[j])
101+
j += 1
102+
else:
103+
R.append(P[i])
104+
i += 1
105+
106+
if i < len(P):
107+
R.extend(P[i:])
108+
else:
109+
R.extend(Q[j:])
110+
111+
return R, num_inversion
112+
113+
114+
def main():
115+
arr_1 = [10, 2, 1, 5, 5, 2, 11]
116+
117+
# this arr has 8 inversions:
118+
# (10, 2), (10, 1), (10, 5), (10, 5), (10, 2), (2, 1), (5, 2), (5, 2)
119+
120+
num_inversions_bf = count_inversions_bf(arr_1)
121+
_, num_inversions_recursive = count_inversions_recursive(arr_1)
122+
123+
assert num_inversions_bf == num_inversions_recursive == 8
124+
125+
print("number of inversions = ", num_inversions_bf)
126+
127+
# testing an array with zero inversion (a sorted arr_1)
128+
129+
arr_1.sort()
130+
num_inversions_bf = count_inversions_bf(arr_1)
131+
_, num_inversions_recursive = count_inversions_recursive(arr_1)
132+
133+
assert num_inversions_bf == num_inversions_recursive == 0
134+
print("number of inversions = ", num_inversions_bf)
135+
136+
# an empty list should also have zero inversions
137+
arr_1 = []
138+
num_inversions_bf = count_inversions_bf(arr_1)
139+
_, num_inversions_recursive = count_inversions_recursive(arr_1)
140+
141+
assert num_inversions_bf == num_inversions_recursive == 0
142+
print("number of inversions = ", num_inversions_bf)
143+
144+
145+
if __name__ == "__main__":
146+
main()
147+
148+

0 commit comments

Comments
 (0)