I have a question about the Python version of recursive Merge Sort. I completed the basic version, which is only referred by the array, and am now working on the index version. I will sink into the endless loop, but I am not sure where I did wrong. Would you mind sharing some ideas? Thank you in advance.
The successful and non-index version:
def mergesort(x):
# The base case is when the array contains less than 1 observation.
length = len(x)
if length < 2:
return x
else:
# Recursive case:merge sort on the lower subarray, and the upper subarray.
mid = (length) // 2
lower = mergesort(x[:mid])
upper = mergesort(x[mid:])
# merge two subarrays.
x_sorted = merge(lower, upper)
return (x_sorted)
def merge(lower, upper):
nlower = len(lower)
nupper = len(upper)
i, j, k = 0, 0, 0
# create a temp array to store the sorted results
temp = [0] * (nlower + nupper)
# as the lower and upper are sorted, since the base case is the single observation.
# now we compare the smallest element in each sorted array, and store the smaller one to the temp array
# repeat this process until one array is completed moved to the temp array
# store the other array to the end of the temp array
while i < nlower and j < nupper:
if lower[i] <= upper[j]:
temp[k] = lower[i]
i += 1
k += 1
else:
temp[k] = upper[j]
j += 1
k += 1
if i == nlower:
temp[i+j:] = upper[j:]
else:
temp[i+j:] = lower[i:]
return temp
With expected results:
x = random.sample(range(0, 30), 15)
mergesort(x)
[0, 1, 3, 6, 9, 10, 11, 13, 14, 17, 18, 20, 25, 27, 29]
But I will stuck into the endless loop with the index version:
def ms(x, left, right):
# the base case: right == left as a single-element array
if left < right:
mid = (left + right) // 2
ms(x, left, mid)
ms(x, mid, right + 1)
m(x, left, mid, right)
return m
def m(x, left, mid, right):
nlower = mid - left
nupper = right - mid + 1
temp = [0] * (nlower + nupper)
ilower, iupper, k = left, mid, 0
while ilower < mid and iupper < right + 1:
if x[ilower] <= x[iupper]:
temp[k] = x[ilower]
ilower += 1
k += 1
else:
temp[k] = x[iupper]
iupper += 1
k += 1
if ilower == mid:
temp[k:] = x[iupper:right+1]
else:
temp[k:] = x[ilower:mid]
x[left:right+1] = temp
return x
The test data as:
x = random.sample(range(0, 30), 15)
ms(x, 0, 14)
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
<ipython-input-59-39859c9eae4a> in <module>
1 x = random.sample(range(0, 30), 15)
----> 2 ms(x, 0, 14)
... last 2 frames repeated, from the frame below ...
<ipython-input-57-854342dcdefb> in ms(x, left, right)
3 if left < right:
4 mid = (left + right)//2
----> 5 ms(x, left, mid)
6 ms(x, mid, right+1)
7 m(x, left, mid, right)
RecursionError: maximum recursion depth exceeded in comparison
Would you mind providing some insights? Thanks.
Your index version uses a confusing convention whereby left is the index of the first element in the slice and right is the index of the last element. This convention requires error-prone +1/-1 adjustments. Your problem is this: mid as computed is the index of the last element in the left half, but you consider mid to be the first element of the right half: a slice of 2 elements is split into one with 0 and one with 2, hence the infinite recursion. You can fix this problem by changing ms(x, mid, right+1) to ms(x, mid+1, right), etc.
Furthermore, retuning m from function ms makes no sense. You should return x if anything at all.
It is much less error prone for right to be the index one past the last element, just like range specifiers in Python. This way there are no confusing +1/-1 adjustments.
Here is modified version:
def ms(x, left, right):
# the base case: right - left as a single-element array
if right - left >= 2:
mid = (left + right) // 2 # index of the first element of the right half
ms(x, left, mid)
ms(x, mid, right)
m(x, left, mid, right)
return x
def m(x, left, mid, right):
nlower = mid - left
nupper = right - mid
temp = [0] * (nlower + nupper)
ilower, iupper, k = left, mid, 0
while ilower < mid and iupper < right:
if x[ilower] <= x[iupper]:
temp[k] = x[ilower]
ilower += 1
k += 1
else:
temp[k] = x[iupper]
iupper += 1
k += 1
if ilower == mid:
temp[k:] = x[iupper:right]
else:
temp[k:] = x[ilower:mid]
x[left:right] = temp
return x
You would invoke as:
x = random.sample(range(0, 30), 15)
ms(x, 0, len(x))
Related
I'm trying to complete an assignment of an Algorithmic course offered by UC San Diego on Coursera and I got a problem...
I used merge sort to count inversions by summing the number of elements left in left array (a array) to the count each time you merge an element from the array in the right (b array), but I don't really know why it doesn't give the correct number of inversions sometimes, and it's driving me nuts, here is the code and some test cases:
import sys
def get_number_of_inversions(a, left, right):
number_of_inversions = 0
# print(a)
if right - left <= 1:
return number_of_inversions
mid = (left + right) // 2
number_of_inversions = get_number_of_inversions(a, left, mid)
number_of_inversions = get_number_of_inversions(a, mid, right)
left_side = a[left:mid]
right_side = a[mid:right]
while len(left_side) > 0 and len(right_side) > 0:
if left_side[0] <= right_side[0]:
a[left] = left_side[0]
left += 1
left_side.remove(left_side[0])
else:
a[left] = right_side[0]
left += 1
right_side.remove(right_side[0])
number_of_inversions += len(left_side)
if len(left_side) == 0:
a[left] = right_side[0]
left += 1
else:
a[left] = left_side[0]
left += 1
return number_of_inversions
if __name__ == '__main__':
n = int(input())
a = list(map(int, input().split()))
print(int(get_number_of_inversions(a, 0, len(a))))
Test cases:
6
9 8 7 3 2 1
This one is supposed to give 15, but it gives 12
6
3 1 9 3 2 1
This one is supposed to give 9, but it gives 8
I have been working hard to produce a 'simple' quick sort algorithm, as so many of the online examples seem to be written more complexly than perhaps required to demonstrate the mechanics of the algorithm.
I have got to a point where I cannot see why the final list is not sorting properly. I would be grateful for a set of fresh eyes and a pointer in the right direction to finish the code.
Here is my current code:
def qsort(dataset):
if len(dataset) < 2:
return (dataset)
else:
point1 = 0
point2 = len(dataset) - 1
pivot = (len(dataset) - 1 ) // 2
while point1 != pivot and point2 != pivot:
while dataset[point1] <= dataset[pivot] and point1 != pivot:
point1 = point1 + 1
while dataset[point2] >= dataset[pivot] and point2 != pivot:
point2 = point2 - 1
x = dataset[point1]
dataset[point1] = dataset[point2]
dataset[point2] = x
left = qsort(dataset[0:pivot])
right = qsort(dataset[(pivot+1):len(dataset)])
return left + [dataset[pivot]] + right
dataset = [45, 29, 56, 23, 55, 27, 43, 46]
print(qsort(dataset))
Any help would be greatly appreciated.
****EDIT**** new code after #Marko Mahnič suggestion but still not sorting correctly. I would be so grateful for any more help that can be offered.
def qsort(dataset):
if len(dataset) < 2:
return (dataset)
else:
point1 = 0
point2 = len(dataset) - 1
pivot = (len(dataset) - 1 ) // 2
while point1 < point2:
while dataset[point1] <= dataset[pivot] and point1 < point2:
point1 = point1 + 1
while dataset[point2] >= dataset[pivot] and point2 > point1:
point2 = point2 - 1
x = dataset[point1]
dataset[point1] = dataset[point2]
dataset[point2] = x
left = qsort(dataset[0:pivot])
right = qsort(dataset[(pivot+1):len(dataset)])
return left + [dataset[pivot]] + right
dataset = [45, 29, 56, 23, 55, 27, 43, 46]
print(qsort(dataset))
quicksort is normally an in-place sort, it sorts the original array instead of returning a sorted copy of the original array. The question's example code is a variation of Hoare partition version of quicksort. Hoare partition splits a partition into elements <= pivot, and elements >= pivot. Elements == to pivot and/or the pivot can end up anywhere, but this isn't an issue, and there is no need to track where the pivot ends up. The run time will generally be less if there are duplicates.
Example standard quicksort with Hoare partition method
def qsort(a, lo, hi):
if(lo >= hi):
return
p = a[(lo + hi) / 2] # pivot, any a[] except a[hi]
i = lo - 1
j = hi + 1
while(1):
while(1): # while(a[++i] < p)
i += 1
if(a[i] >= p):
break
while(1): # while(a[--j] < p)
j -= 1
if(a[j] <= p):
break
if(i >= j):
break
a[i],a[j] = a[j],a[i]
qsort(a, lo, j)
qsort(a, j+1, hi)
dataset = [45, 29, 56, 23, 55, 27, 43, 46]
qsort(dataset, 0, len(dataset)-1) # last param is len-1
print(dataset)
Here is an iterative version of your algorithm. It uses a stack to eliminate the need for the recursive calls. Using the last element as the pivot makes it simpler too:
def qsort(dataset):
low = 0
high = len(dataset) - 1
# stack tio use instead of the stack of recursive function calls
stack = [0] * len(dataset)
top = -1
# stack initial values
top += 1
stack[top] = low
top += 1
stack[top] = high
# while the stack is not empty
while top >= 0:
# pop high and low
high = stack[top]
top -= 1
low = stack[top]
top -= 1
# partition by the pivot
i = low - 1
x = dataset[high] # using the last element as pivot
for j in range(low, high):
if dataset[j] <= x:
# increment index of smaller element
i += 1
dataset[i], dataset[j] = dataset[j], dataset[i]
dataset[i + 1], dataset[high] = dataset[high], dataset[i + 1]
p = i + 1 # updates the pivot position
# If there are elements on left side of pivot,
# then push left side to stack
if p - 1 > low:
top += 1
stack[top] = low
top += 1
stack[top] = p - 1
# If there are elements on right side of pivot,
# then push right side to stack
if p + 1 < high:
top += 1
stack[top] = p + 1
top += 1
stack[top] = high
return dataset
dataset = [45, 29, 56, 23, 55, 27, 43, 46]
print(qsort(dataset))
And you could make it more readable by extracting the stack manipulation and the partitioning to separate functions.
The conditions should be point1 < point2, not pointX != pivot. The split to left and right should be where point1 and point2 meet.
Note that in qsort the important part of the pivot is its value, not its index. You can choose the value of any element from dataset as the pivot. When partitioning is done the elements left of pivot2 will be lower or equal to the pivot's value and the elements right of pivot1 will be higher or equal to the pivot's value.
You can adjust pivot1 and pivot2 before the recursive call so that the left array contains only the elements lower than the pivot, the middle array contains the elements with the pivot's value and the right array contains the elements higher than the pivot's value. This is a slight optimization. The result is then left + middle + right. This is especially useful when dataset has duplicated elements.
These changes will make your algorithm correct but not optimal. The problem is that the algorithm is making copies of parts of the original list which requires O(n^2) extra space which is (n-1)*(n-2)/2 in the worst case when it selects the highest or the lowest value as the pivot. So instead of sorting full lists, the algorithm should be changed to sort part of the list in-place. Its interface should be changed to:
def qsort( a, left, right ):
and the recursive calls should be:
qsort( a, left, pivot2 )
qsort( a, pivot1, right )
Another optimization is to reduce the number of recursive calls since they are not cheap. When the size of the input list falls below a certain small value (8 for example), the list can be sorted with a simpler O(n^2) sorting algorithm like Simple Insertion, Simple Selection, Bubble Sort or similar.
OK, so I have worked out the issue.
Everything was fine except for the fact that I was not updating the location of the pivot if it was indeed the value at the pivot that was swapped during an iteration. I therefore added a couple of flags in the event of the pivot being swapped and updated the pivot location after the swap before the next split occurred.
Here is the code if anyone is interested. Of course would love the flags etc to be more tidy if any of you have any ideas?
def qsort(dataset):
pivotatp1 = False
pivotatp2 = False
if len(dataset) < 2:
return (dataset)
else:
point1 = 0
point2 = len(dataset) - 1
pivot = (len(dataset) - 1 ) // 2
while point1 != pivot and point2 != pivot:
while dataset[point1] <= dataset[pivot] and point1 != pivot:
point1 = point1 + 1
while dataset[point2] >= dataset[pivot] and point2 != pivot:
point2 = point2 - 1
if pivot == point1:
pivotatp1 = True
if pivot == point2:
pivotatp2 = True
x = dataset[point1]
dataset[point1] = dataset[point2]
dataset[point2] = x
if pivotatp1 == True:
pivot = point2
if pivotatp2 == True:
pivot = point1
left = qsort(dataset[0:pivot])
right = qsort(dataset[(pivot+1):len(dataset)])
return left + [dataset[pivot]] + right
dataset = [45, 29, 56, 23, 55, 27, 43, 46]
print(qsort(dataset))
This is my attempted implementation of quicksort in python
However, I notice that I have a max call stack exceeded error, likely because the length of my collection never goes down.
When printing, I notice that my recursive call does not update the "end" variable
def swap(collection, index1, index2):
temp = collection[index1]
collection[index1] = collection[index2]
collection[index2] = temp
def partition(collection, idx1, idx2):
left = idx1
right = idx2
pivot = len(collection)//2
while(left < right):
while(collection[left] < collection[pivot]):
left += 1
while(collection[right] > collection[pivot]):
right -= 1
if left < right:
swap(collection, left, right)
left +=1
right -=1
return left
def quickSort(collection, start, end):
#Check for base case
if len(collection) > 1:
index = partition(collection, start, end)
newIdx = index - 1
if start < newIdx:
quickSort(collection, start, newIdx)
if index < right:
quickSort(collection, index, end)
return collection
array = [7,5, 10, 12, 3,2,9]
quickSort(array, 0, len(array) - 1)
print(array)
Below is the code that I'm implementing for quick sort in python. But I'm not able to get where I'm going wrong with the implementation. Any leads?
def quicksort(arr, size):
partition(arr, size)
def partition(arr, size):
if size <= 1:
return
left = 0
right = size - 1
pivot = arr[size/2]
while left < right:
while arr[left] < pivot:
left += 1
while arr[right] > pivot:
right -= 1
temp = arr[left]
arr[left] = arr[right]
arr[right] = temp
partition(arr, left)
partition(arr[left:], len(arr[left:]))
arr = [1,2,3,4,5,45,3,5,4,6]
quicksort(arr, len(arr))
You are sorting new slices:
partition(arr[left:], len(arr[left:]))
Those slices are new lists, not the original list object. Anything you do to those won't be visible in the original value of arr.
If you want to do the sorting in-place, you'll need to pass along start and stop values of what section of the array to sort, not pass along slices:
def quicksort(arr):
partition(arr, 0, len(arr) - 1)
def partition(arr, start, stop):
if stop - start < 1:
return
left = start
right = stop
pivot = arr[start + ((stop - start) / 2)]
while left <= right:
while arr[left] < pivot:
left += 1
while arr[right] > pivot:
right -= 1
if left <= right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
partition(arr, start, right)
partition(arr, left, stop)
I've made a few more tweaks, adjusting the boundary tests and using tuple assignment to swap the two elements.
This version sorts correctly:
>>> def quicksort(arr):
... partition(arr, 0, len(arr) - 1)
...
>>> def partition(arr, start, stop):
... if stop - start < 1:
... return
... left = start
... right = stop
... pivot = arr[start + ((stop - start) / 2)]
... while left <= right:
... while arr[left] < pivot:
... left += 1
... while arr[right] > pivot:
... right -= 1
... if left <= right:
... arr[left], arr[right] = arr[right], arr[left]
... left += 1
... right -= 1
... partition(arr, start, right)
... partition(arr, left, stop)
...
>>> arr = [1,2,3,4,5,45,3,5,4,6]
>>> quicksort(arr)
>>> arr
[1, 2, 3, 3, 4, 4, 5, 5, 6, 45]
I have what should be a simple quicksort implementation, but it's returning a recursion depth exceeded error, and I'm testing it on a list of less than 30 elements. Moreover, my implementation was working on a list of 10,000 a few days ago, and the only thing I changed was moving it from a Class to a global function. Anyone see what may be causing this?
def quickSort(m, left, right):
if len(m[left:right]) <= 1:
return m
pivot = m[left]
i = left + 1
j = left + 1
for j in range(j, right):
if m[j] <= pivot:
m[j], m[i] = m[i], m[j]
i += 1
m[left], m[i-1] = m[i-1], m[left]
m = quickSort(m, left, i)
m = quickSort(m, i, right)
return m
one of your recursive calls is causing the exception(as you may have guessed :-), also note that you sort the list in place so returning the list is not necessary
def quickSort(m, left, right):
if right - left <= 1:
return
pivot = m[left]
i = left + 1
j = left + 1
for j in range(j, right):
if m[j] <= pivot:
m[j], m[i] = m[i], m[j]
i += 1
m[left], m[i-1] = m[i-1], m[left]
quickSort(m, left, i-1)
quickSort(m, i, right)