Skip to content

Commit 5bdcd48

Browse files
maxwell-aladagocclauss
authored andcommitted
EHN: A divide-and-conquer, and brute-force algorithms for array inversions co… (#1133)
* divide and conquer and brute force algorithms for array-inversions counting * divide and conquer and brute force algorithms for array-inversions counting * divide and conquer and brute force algorithms for array-inversions counting
1 parent 3e69733 commit 5bdcd48

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

Diff for: divide_and_conquer/inversions.py

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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+
Examples
30+
---------
31+
32+
>>> count_inversions_bf([1, 4, 2, 4, 1])
33+
4
34+
>>> count_inversions_bf([1, 1, 2, 4, 4])
35+
0
36+
>>> count_inversions_bf([])
37+
0
38+
"""
39+
40+
num_inversions = 0
41+
n = len(arr)
42+
43+
for i in range(n-1):
44+
for j in range(i + 1, n):
45+
if arr[i] > arr[j]:
46+
num_inversions += 1
47+
48+
return num_inversions
49+
50+
51+
def count_inversions_recursive(arr):
52+
"""
53+
Counts the number of inversions using a divide-and-conquer algorithm
54+
55+
Parameters
56+
-----------
57+
arr: array-like, the list containing the items for which the number
58+
of inversions is desired. The elements of `arr` must be comparable.
59+
60+
Returns
61+
-------
62+
C: a sorted copy of `arr`.
63+
num_inversions: int, the total number of inversions in 'arr'
64+
65+
Examples
66+
--------
67+
68+
>>> count_inversions_recursive([1, 4, 2, 4, 1])
69+
([1, 1, 2, 4, 4], 4)
70+
>>> count_inversions_recursive([1, 1, 2, 4, 4])
71+
([1, 1, 2, 4, 4], 0)
72+
>>> count_inversions_recursive([])
73+
([], 0)
74+
"""
75+
if len(arr) <= 1:
76+
return arr, 0
77+
else:
78+
mid = len(arr)//2
79+
P = arr[0:mid]
80+
Q = arr[mid:]
81+
82+
A, inversion_p = count_inversions_recursive(P)
83+
B, inversions_q = count_inversions_recursive(Q)
84+
C, cross_inversions = _count_cross_inversions(A, B)
85+
86+
num_inversions = inversion_p + inversions_q + cross_inversions
87+
return C, num_inversions
88+
89+
90+
def _count_cross_inversions(P, Q):
91+
"""
92+
Counts the inversions across two sorted arrays.
93+
And combine the two arrays into one sorted array
94+
95+
For all 1<= i<=len(P) and for all 1 <= j <= len(Q),
96+
if P[i] > Q[j], then (i, j) is a cross inversion
97+
98+
Parameters
99+
----------
100+
P: array-like, sorted in non-decreasing order
101+
Q: array-like, sorted in non-decreasing order
102+
103+
Returns
104+
------
105+
R: array-like, a sorted array of the elements of `P` and `Q`
106+
num_inversion: int, the number of inversions across `P` and `Q`
107+
108+
Examples
109+
--------
110+
111+
>>> _count_cross_inversions([1, 2, 3], [0, 2, 5])
112+
([0, 1, 2, 2, 3, 5], 4)
113+
>>> _count_cross_inversions([1, 2, 3], [3, 4, 5])
114+
([1, 2, 3, 3, 4, 5], 0)
115+
"""
116+
117+
R = []
118+
i = j = num_inversion = 0
119+
while i < len(P) and j < len(Q):
120+
if P[i] > Q[j]:
121+
# if P[1] > Q[j], then P[k] > Q[k] for all i < k <= len(P)
122+
# These are all inversions. The claim emerges from the
123+
# property that P is sorted.
124+
num_inversion += (len(P) - i)
125+
R.append(Q[j])
126+
j += 1
127+
else:
128+
R.append(P[i])
129+
i += 1
130+
131+
if i < len(P):
132+
R.extend(P[i:])
133+
else:
134+
R.extend(Q[j:])
135+
136+
return R, num_inversion
137+
138+
139+
def main():
140+
arr_1 = [10, 2, 1, 5, 5, 2, 11]
141+
142+
# this arr has 8 inversions:
143+
# (10, 2), (10, 1), (10, 5), (10, 5), (10, 2), (2, 1), (5, 2), (5, 2)
144+
145+
num_inversions_bf = count_inversions_bf(arr_1)
146+
_, num_inversions_recursive = count_inversions_recursive(arr_1)
147+
148+
assert num_inversions_bf == num_inversions_recursive == 8
149+
150+
print("number of inversions = ", num_inversions_bf)
151+
152+
# testing an array with zero inversion (a sorted arr_1)
153+
154+
arr_1.sort()
155+
num_inversions_bf = count_inversions_bf(arr_1)
156+
_, num_inversions_recursive = count_inversions_recursive(arr_1)
157+
158+
assert num_inversions_bf == num_inversions_recursive == 0
159+
print("number of inversions = ", num_inversions_bf)
160+
161+
# an empty list should also have zero inversions
162+
arr_1 = []
163+
num_inversions_bf = count_inversions_bf(arr_1)
164+
_, num_inversions_recursive = count_inversions_recursive(arr_1)
165+
166+
assert num_inversions_bf == num_inversions_recursive == 0
167+
print("number of inversions = ", num_inversions_bf)
168+
169+
170+
if __name__ == "__main__":
171+
main()
172+
173+

0 commit comments

Comments
 (0)