I have implemented a mergesort function which works correctly, However, I'm having a hard time modifying it to count the number of inversions in the original array before it is sorted.
An inversion is a pair where i < j but a[i] > a[j] an example, a = [5,2,1] has 3 inversions: (5,2),(5,1),(2,1)
def mergeSort(a):
mid = len(a)//2
if len(a) < 2:
return
l = a[:mid]
r = a[mid:]
mergeSort(l)
mergeSort(r)
return merge(l,r,a)
def merge(l,r,a):
i = 0
j = 0
k = 0
inv = 0
while(i < len(l) and j < len(r)):
if(l[i] < r[j]):
a[k] = l[i]
i = i + 1
else:
a[k] = r[j]
inv = inv + 1
j = j + 1
k = k + 1
while i < len(l):
a[k] = l[i]
i = i + 1
k = k + 1
while j < len(r):
a[k] = r[j]
j = j + 1
k = k + 1
inv = inv + 1
return [a,inv]
a = [6,5,4,3,2,1]
print(mergeSort(a))
The above example should return 15 as the count of inversions as n(n-1)/2 is the number of inversions for descending order array.
can someone explain how to count it?
L[i] > R[j] is a single inversion, but note that since the arrays are sorted, if L[k] > R[j] for some k, this means L[k] > R[j] for all i <= k < |L|. So you can subtract the length of the array L from i to give you the total number of inversions.
Related
I am trying to implement python Merge Sort, but for some reason when merging it does not sort it correctly at all. I am trying to turn Pseudo code into Python code and I am failing miserably. If anyone can help, I'd really appreciate it. I have tried to debug it and I am just confused.
def mergeSort(A, p, r):
if p < r:
q = int(((p + (r-1)) / 2))
mergeSort(A, p, q)
mergeSort(A, q + 1, r)
merge(A, p, q, r)
def merge(A, p, q, r): # Issue with sorting
n1 = q - p + 1
n2 = r - q
L = [0] * n1
R = [0] * n2
for i in range(0, n1):
L[i] = A[p + i]
for j in range(0, n2):
R[j] = A[q + j]
L.append(infinity)
R.append(infinity)
i = 0
j = 0
for k in range(p,r):
if L[i] <= R[j]:
A[k] = L[i]
i = i + 1
else:
A[k] = R[j]
j = j + 1
Example Input:
['date', 'apple', 'banana', 'cucumber', 'acorn', 'aaaa']
Example Output:
['banana', 'acorn', 'aaaa', 'cucumber', 'date', 'date']
mergeSort psuedocode
merge pseudocode
The issue with your implementation is that you are not properly updating the indices when merging the two sublists back together. In the following lines:
for k in range(p,r):
if L[i] <= R[j]:
A[k] = L[i]
i = i + 1
else:
A[k] = R[j]
j = j + 1
you should be incrementing k instead of i and j. Also the condition inside of the if statement should be L[i] <= R[j] instead of L[i] >= R[j].
Here is the corrected version of the merge function:
def merge(A, p, q, r):
n1 = q - p + 1
n2 = r - q
L = [0] * n1
R = [0] * n2
for i in range(0, n1):
L[i] = A[p + i]
for j in range(0, n2):
R[j] = A[q + j]
i = 0
j = 0
k = p
while i < n1 and j < n2:
if L[i] <= R[j]:
A[k] = L[i]
i += 1
else:
A[k] = R[j]
j += 1
k += 1
while i < n1:
A[k] = L[i]
i += 1
k += 1
while j < n2:
A[k] = R[j]
j += 1
k += 1
Also, you have to import the math library and use math.inf instead of infinity.
It takes the random list I generated but the output is something a lot different than I expected.
I think that something goes wrong with merging.
e.g. : Input --> [267,168,236,190,2,500,4,45,86]
Output --> [2,2,2,2,2,4,4,4,45,45,86]
Thank you in advance.
import numpy as np
def mergeSort(myList):
if len(myList) > 1:
mid = len(myList) // 2
left = myList[:mid]
right = myList[mid:]
# Recursive call on each half
mergeSort(left)
mergeSort(right)
# Two iterators for traversing the two halves
i = 0
j = 0
# Iterator for the main list
k = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
# The value from the left half has been used
myList[k] = left[i]
# Move the iterator forward
i += 1
else:
myList[k] = right[j]
j += 1
# Move to the next slot
k += 1
# For all the remaining values
while i < len(left):
myList[k] = left[i]
i += 1
k += 1
while j < len(right):
myList[k]=right[j]
j += 1
k += 1
list1 = np.random.randint(low=1, high=800, size=100)
myList = list1
print("Given array is", end="\n")
print(myList)
mergeSort(myList)
print("Sorted array is: ", end="\n")
print(myList)
The problem with your code is only with inplace manipulation of your original list. merge sort needs extra space O(n).
you could simply rewrite your code like this to work: (notice that it is your code, only I modified two lines, look for # changed ...)
import numpy as np
def mergeSort(myList):
if len(myList) > 1:
mid = len(myList) // 2
left = myList[:mid].copy() # changed this line
right = myList[mid:].copy() # changed this line
# Recursive call on each half
mergeSort(left)
mergeSort(right)
# Two iterators for traversing the two halves
i = 0
j = 0
# Iterator for the main list
k = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
# The value from the left half has been used
myList[k] = left[i]
# Move the iterator forward
i += 1
else:
myList[k] = right[j]
j += 1
# Move to the next slot
k += 1
# For all the remaining values
while i < len(left):
myList[k] = left[i]
i += 1
k += 1
while j < len(right):
myList[k]=right[j]
j += 1
k += 1
myList = np.random.randint(low=1, high=800, size=100)
print("Given array is", end="\n")
print(myList)
mergeSort(myList)
print("Sorted array is: ", end="\n")
print(myList)
However this is not very optimized version of implementation for the mergsort specially in python.
Here is better implementation, pure python (from: https://github.com/amirhm/algo-data-scratch/blob/main/Sorting/mergesort.ipynb)
def mergesort(l):
def merge(l, r):
lp, rp = 0 , 0
d = []
while lp < len(l) and rp < len(r):
if l[lp] < r[rp]:
d.append(l[lp])
lp += 1
else:
d.append(r[rp])
rp += 1
if rp < len(r): d.extend(l[lp:])
if lp < len(l): d.extend(r[rp:])
return d
if len(l) <= 1:
return l
n = len(l)
return merge(mergesort(l[:n//2]), mergesort(l[n//2:]))
or much more abstract only in 9 lines:
def mergesort(l):
def merge(l, r):
res = []
while l and r : ((res.append(l.pop())) if (l[-1] > r[-1]) else res.append(r.pop()))
#while r or l: res.append(r.pop()) if r else (res.append(l.pop()))
if r: res[::-1].extend(r)
if l: res[::-1].extend(l)
return res
if len(l) <= 1: return l
return merge(mergesort(l[:len(l) // 2]), mergesort(l[len(l) // 2:]))
I need to convert pseudocode into a merge sort algorithm that mirrors that pseudocode. I am new to pseudocode so I'm having trouble with this. Can anyone tell me what is wrong with my algorithm? Please note that the arrays in the pseudocode are 1-indexed.
PSEUDOCODE:
MergeSort(A[1 .. n]):
if n > 1
m ← bn/2c
MergeSort(A[1 .. m])
MergeSort(A[m + 1 .. n])
Merge(A[1 .. n], m)
Merge(A[1 .. n], m):
i ← 1; j ← m + 1
for k ← 1 to n
if j > n
B[k] ← A[i]; i ← i + 1
else if i > m
B[k] ← A[j]; j ← j + 1
else if A[i] < A[ j]
B[k] ← A[i]; i ← i + 1
else
B[k] ← A[j]; j ← j + 1
for k ← 1 to n
A[k] ← B[k]
MY CODE
def mergeSort(arr):
n = len(arr)
if n > 1:
m = n//2
mergeSort(arr[:m])
mergeSort(arr[m:])
merge(arr, m)
def merge(arr, m):
n = len(arr)
i = 0
j = m
b = [0] * n
for k in range(n):
if j >= n:
b[k] = arr[i]
i += 1
elif i > m-1:
b[k] = arr[j]
j += 1
elif arr[i] < arr[j]:
b[k] = arr[i]
i += 1
else:
b[k] = arr[j]
j += 1
for k in range(n):
arr[k] = b[k]
The thing is that in the pseudo code version, the notation A[1..m] is supposed to mean a partition of the array, but in-place, not as a new array (slice): it is like a window on a part of the array, with its own indexing, but not copied.
The translation to list slicing in Python does not reflect this. arr[:m] creates a new list, and so whatever mergeSort(arr[:m]) does with that new list, it doesn't touch arr itself: all that work is for nothing, as it doesn't mutate arr, but a sliced copy of it, which we lose access to.
A solution is to not create slices, but to pass the start/end indices of the intended partition to the function call.
Here is the adapted code:
def mergeSort(arr):
mergeSortRec(arr, 0, len(arr))
def mergeSortRec(arr, start, end):
n = end - start
if n > 1:
m = start + n//2
mergeSortRec(arr, start, m)
mergeSortRec(arr, m, end)
merge(arr, start, m, end)
def merge(arr, start, m, end):
n = end - start
i = start
j = m
b = [0] * n
for k in range(n):
if j >= end:
b[k] = arr[i]
i += 1
elif i >= m:
b[k] = arr[j]
j += 1
elif arr[i] < arr[j]:
b[k] = arr[i]
i += 1
else:
b[k] = arr[j]
j += 1
for k in range(n):
arr[start + k] = b[k]
This code is working fine for me, however your operations are being applied in place, so you just need to call the function with the array to sort rather than getting the return value (which will always be None, because you provide no return in the function mergeSort)
arr = np.random.uniform(1, 10, 10)
print(arr)
[2.10748505 9.47408117 5.4620788 1.5585025 9.57387679 4.13719947
1.28671255 4.150946 2.84044402 6.56294717]
mergeSort(arr)
print(arr)
[1.28671255 1.5585025 2.10748505 2.84044402 4.13719947 4.150946
5.4620788 6.56294717 9.47408117 9.57387679]
I was given the task with the merge-insertion sort described as(paraphrased):
Starting off with merge sort, once a threshold S(small positive integer) is reached, the algorithm will then sort the sub arrays with insertion sort.
We are tasked to find the optimal S value for varying length of inputs to achieve minimum key comparisons. I implemented the code by modifying what was available online to get:
def mergeSort(arr, l, r, cutoff):
if l < r:
m = l+(r-l)//2
if len(arr[l:r+1]) > cutoff:
return mergeSort(arr, l, m, cutoff)+mergeSort(arr, m+1, r, cutoff)+merge(arr, l, m, r)
else:
return insertionSort(arr, l, r+1)
return 0
def merge(arr, l, m, r):
comp = 0
n1 = m - l + 1
n2 = r - m
L = [0] * (n1)
R = [0] * (n2)
for i in range(0, n1):
L[i] = arr[l + i]
for j in range(0, n2):
R[j] = arr[m + 1 + j]
i = 0
j = 0
k = l
while i < n1 and j < n2:
if L[i] <= R[j]:
arr[k] = L[i]
i += 1
else:
arr[k] = R[j]
j += 1
k += 1
comp +=1
while i < n1:
arr[k] = L[i]
i += 1
k += 1
while j < n2:
arr[k] = R[j]
j += 1
k += 1
return comp
def insertionSort(arr, l, r):
comp = 0
for i in range(l+1, r):
key = arr[i]
j = i-1
while j >= l:
if key >= arr[j]:
comp += 1
break
arr[j + 1] = arr[j]
j -= 1
comp += 1
arr[j + 1] = key
return comp
However the graph I get for the minimum value of S against length is:
This means that a near-pure mergesort is almost always preferred over the hybrid. Which is against what is available online, saying that insertion sort will perform faster than mergesort at low values of S(~10-25). I can't seem to find any error with my code, so is hybrid sort really better than merge sort?
IMO the question is flawed.
Mergesort always performs N Lg(N) key comparisons, while Insertionsort takes N²/2 of them. Hence as of N=2, the comparison count favors Mergesort in all cases. (This is only approximate, as N does not always divide evenly).
But the number of moves as well as the overhead will tend to favor Insertionsort. So a more relevant metric is the actual running time which, unfortunately, will depend on the key length and type.
Q- I have an array A with the sizes of apples and have to create another array S which would contain the indices of the apples in sorted order given that we cannot directly access or touch A only a function is_large(A,i,j) function can access it. It returns -1 is A[i] > A[j] and 1 if A[i] < A[j].
I wrote the program but it is giving incorrect results even for small arrays what is the problem? The main problem is that the first element is not changing positions because of that the whole array is unsorted.
def is_large_apples(apple_size, i, j):
""" Takes two indices and tells
which one of them is larger or smaller """
if apple_size[i] > apple_size[j]:
return 1
elif apple_size[i] < apple_size[j]:
return -1
def mergesort_apples(s, l, r):
""" This function takes indexed list and
makes recursive calls to sort the array """
if l < r:
mid = (l+r)//2
mergesort_apples(s, l, mid)
mergesort_apples(s, mid+1, r)
merge_apples(s, l, mid, r)
def merge_apples(s, l, mid, r):
""" This function takes the list and
the indices to merge them into the final array"""
nl = mid - l + 1
nr = r - mid
left, right = [], []
left = s[l:mid+1:1]
right = s[mid+1:r+1:1]
i, j, k = 0, 0, l;
while i < nl and j < nr:
print(s)
if is_large_apples(apple_size, i, j) == -1:
s[k] = left[i]
i += 1
else:
s[k] = right[j]
j += 1
k += 1
while i < nl:
s[k] = left[i]
k += 1
i += 1
while j < nr:
s[k] = right[j]
k += 1
j += 1
apple_size = [5, 7, 1,44,2,33] # Given list of sizes.
s = [x for x in range(0,len(apple_size))] # Original list of indices.
mergesort_apples(s, 0, len(s)-1)
print(s)
if is_large_apples(apple_size, left[i], right[j]) == -1:
Because you want to check not i and j position, but left[i] position and right[j] position.