Python - MergeSort Recursion Error - python

I made a MergeSort program in python using recursion and I keep getting errors about line 27,line 23,line 18 and a recursion error:
"RecursionError: maximum recursion depth exceeded in comparison" but i don't seem to find any obvious mistake in my code.
def merge(list, start, middle, end):
L = list[start:middle]
R = list[middle:end+1]
L.append(99999999999)
R.append(99999999999)
i = j = 0
for k in range(start, end+1):
if L[i] <= R[j]:
list[k] = L[i]
i += 1
else:
list[k] = R[j]
j+=1
def mergesort2(list, start, end):
if start < end:
middle = (start + end)//2
mergesort2(list, start, end)
mergesort2(list, middle+1, end)
merge(list, start, middle, end)
def mergesort(list):
mergesort2(list, 0, len(list)-1)
mylist = [8,23,4,56,75,21,42,10,2,5]
mergesort(mylist)
print(mylist)
Thanks

def mergesort2(list, start, end):
if start < end:
middle = start + (end - start)//2
mergesort2(list, start, middle) // notice middle instead of end.
mergesort2(list, middle+1, end)
merge(list, start, middle, end)
You were recursing with the same list without reducing its size, thus it was never reaching the base case.
Edit:
Also, middle should be calculated by start + (end-start)/2, instead of (start+end)/2, to avoid integer overflow errors when using large arrays. It's a good practice.
Edit 2:
After analysing the code even more, I found that the output was wrong. I have tried to correct them and this is my code:
def merge(start, middle, end):
L = l[:middle]
R = l[middle:]
i = j = k = 0
while i < len(L) and j < len(R):
if L[i] <= R[j]:
l[k] = L[i]
i += 1
else:
l[k] = R[j]
j+=1
k += 1
while i < len(L):
l[k] = L[i]
i += 1
k += 1
while j < len(R):
l[k] = R[j]
j += 1
k += 1
def mergesort2(start, end):
if start < end:
middle = start + (end - start)//2
mergesort2(start, middle)
mergesort2(middle+1, end)
merge(start, middle, end)
def mergesort(l):
mergesort2(0, len(l)-1)
l = [8,23,4,56,75,21,42,10,2,5]
mergesort(l)
print(l)
A few points to be noted:
Changed the variable name from list to l to avoid confusion with the keyword list.
There was no use passing the list to every function because it was already declared as a global variable.
merge() had some issues. The loop should actually run from 0 till the length of both the lists are not crossed. If crossed, then just copy the rest of the elements.
Used proper Python splicing techniques :-p

Related

Describing QuickSort Algoritm

Im having a problem to understand this algorithm, how would you describe it, mostly the while loops. I understand that this i an iterative function, and the use of Hoare partition scheme. But what does the while loops do, and why does it use breaks.
def inplace_quick_sort_nonrecursive(S):
if len(S) < 2:
return S
stack = [] #initialize the stack
stack.append([0, len(S) - 1])
while len(stack) > 0: #loop till the stack is empty
low, high = stack.pop() #pop low and high indexes
pivot = S[(low + high) // 2] #create pivot, of any S[] except S[high]
i = low - 1 #Hoare partition
j = high + 1
while True:
while True: #while (S[++i] < pivot)
i += 1
if(S[i] >= pivot):
break
while True: #while(S[--j] < p)
j -= 1
if(S[j] <= pivot):
break
if (i >= j): #if indexes met or crossed, break
break
S[i], S[j] = S[j], S[i] #else swap the elements
if (j > low): #push the indexes into the stack
stack.append([low, j])
j += 1
if (high > j):
stack.append([j, high])

Merge sort in python: slicing vs iterating - impact on complexity

I want to check that my understanding of how python handles slices is correct.
Here's my implementation of merge sort:
def merge_sort(L):
def merge(a, b):
i, j = 0, 0
c = []
while i < len(a) and j < len(b):
if a[i] < b[j]:
c.append(a[i])
i += 1
elif b[j] < a[i]:
c.append(b[j])
j += 1
if a[i:]:
c.extend(a[i:])
if b[j:]:
c.extend(b[j:])
return c
if len(L) <= 1:
return L
else:
mid = len(L) // 2
left = merge_sort(L[:mid])
right = merge_sort(L[mid:])
return merge(left, right)
Am I right in thinking that I could replace this:
if a[i:]:
c.extend(a[i:])
if b[j:]:
c.extend(b[j:])
With this:
while i < len(a):
c.append(a[i])
i += 1
while j < len(b):
c.append(b[j])
j += 1
And have the exact same level of complexity? My understanding of slicing is that its complexity is equivalent to slice length? Is that correct?
Does the fact that I'm calling a slice twice (first in the condition, second time inside of it) make it 2x complexity?
Your implementation of mergesort has problems:
in the merge function's main loop, you do nothing if the values in a[i] and b[j] are equal, or more precisely if you have neither a[i] < b[i] nor a[i] > b[i]. This causes an infinite loop.
there is no need to define merge as a local function, actually there is no need to make it a separate function, you could inline the code and save the overhead of a function call.
Here is a modified version:
def merge_sort(L):
if len(L) <= 1:
return L
else:
mid = len(L) // 2
a = merge_sort(L[:mid])
b = merge_sort(L[mid:])
i, j = 0, 0
c = []
while i < len(a) and j < len(b):
if a[i] <= b[j]:
c.append(a[i])
i += 1
else:
c.append(b[j])
j += 1
if a[i:]:
c.extend(a[i:])
else:
c.extend(b[j:])
return c
Regarding performance, slicing or iterating has no impact on complexity since both operations have linear time cost.
Regarding performance, here are directions to try:
replace the test if a[i:] with if i < len(a). Creating the slice twice is costly.
perform the sort in place, avoiding the append operations
restructure the main loop to have a single test per iteration
Here is a modified version:
def merge_sort(L):
if len(L) <= 1:
return L
else:
mid = len(L) // 2
a = merge_sort(L[:mid])
b = merge_sort(L[mid:])
i, j, k = 0, 0, 0
while True:
if a[i] <= b[j]:
L[k] = a[i]
k += 1
i += 1
if (i == len(a)):
L[k:] = b[j:]
return L
else:
L[k] = b[j]
k += 1
j += 1
if (j == len(b)):
L[k:] = a[i:]
return L

I wrote a merge sort but it ends up in an infinite loop

If the break rule is true in the first round, the program stops, but when it gets into the loop, it never stops
def mSort(liste):
mSortHelp(liste, 0, len(liste)-1)
return liste
def mSortHelp(liste, first, last):
if first < last:
mid = (first+last)//2
mSortHelp(liste, mid, last)
mSortHelp(liste, fist, mid)
merge(liste, first, mid, last)
return liste
def merge(liste, first, mid, last):
LeftList = liste[first:mid]
RightList =lListe[mid:last+1]
i=j=0
for k in range (first, last):
if LeftList[i] <= RightList[j]:
liste[k] = LeftList[i]
i+=1
else:
liste[k] = RightList[j]
j+=1
return liste
print(mSort(liste))
I hope somebody can help fix my infinite loop and give a sorted list (by merge sort) back.
First, infinit loop is caused by if first < last:, for example, if first=2, last=3, it will never break through it, if you change it to first < last-1, it will cause another problem, two len list will remain unsort. So the best way to solve this problem is make last no include, like [first, last).
And there are other problems in your program, such as syntax problem, index out of range in merge, and other problems.
I have fixed them with comment:
def mSort(liste):
# change the last index
# mSortHelp(liste, 0, len(liste) - 1)
mSortHelp(liste, 0, len(liste))
return liste
def mSortHelp(liste, first, last):
# here is the key point causes infinit loop, such as first=2 last = 3
# if first < last:
if first < last - 1:
mid = (first + last) // 2
mSortHelp(liste, mid, last)
mSortHelp(liste, first, mid)
merge(liste, first, mid, last)
return liste
def merge(liste, first, mid, last):
LeftList = liste[first:mid]
# change the last index
# RightList = liste[mid:last + 1]
RightList = liste[mid:last]
i = j = 0
print(LeftList, RightList)
# here should be last, which means [first, last+1)
for k in range(first, last):
# here will cause "IndexError: list index out of range", if i or j reach the end
# if LeftList[i] <= RightList[j]:
if i < len(LeftList) and j < len(RightList):
if LeftList[i] <= RightList[j]:
liste[k] = LeftList[i]
i += 1
else:
liste[k] = RightList[j]
j += 1
# when one list reach the end
else:
if i < len(LeftList):
liste[k] = LeftList[i]
i += 1
else:
liste[k] = RightList[j]
j += 1
print(liste[first: last])
# return is not necessary here, has been changed by parameter reference
# return liste
Hope that will help you, and comment if you have further questions. : )
Example code where reaching the end of either sub-list is handled in the main merge loop, followed by a break, (instead of handling the copy after the loop).
def mergesort(a,beg,end):
if (end-beg) > 1:
mid=(beg+end)//2
mergesort(a,beg,mid)
mergesort(a,mid,end)
merge(a,beg,mid,end)
def merge(a,beg,mid,end):
left = a[beg:mid]
right = a[mid:end]
i = 0
j = 0
k = beg
while True:
if left[i] <= right[j]:
a[k] = left[i]
i += 1
k += 1
if(i < len(left)):
continue
a[k:end] = right[j:len(right)]
break
else:
a[k] = right[j]
j += 1
k += 1
if(j < len(right)):
continue
a[k:end] = left[i:len(left)]
break

Merge sort in python without slicing using recursion

Here is my solution to a merge sort in python.
My notes:
The time is 2n(log(n)) instead of n(log(n)) because of the stack in recursion. I can't see any way to fix this.
A bottom up recursion method is actually easier and is n(log(n))
I have seen my solution that use slicing. For example:
lefthalf = alist[:mid]
righthalf = alist[mid:]
mergeSort(lefthalf)
mergeSort(righthalf)
However, these solutions ignore that a slice takes k time, because a slice takes k times. So the above code actually is:
lefthalf = []
for i in range(mid): #k times
lefhalf.append(alist[i])
for i in range(mid, len(alist): #k times
righthalf.append(alist[i])
My solution:
import random
def _merge_sort(indices, the_list):
start = indices[0]
end = indices[1]
half_way = (end - start)//2 + start
if start < half_way:
_merge_sort((start, half_way), the_list)
if half_way + 1 <= end and end - start != 1:
_merge_sort((half_way + 1, end), the_list)
#a stack is created using log(n) number of recursions
sort_sub_list(the_list, indices[0], indices[1])
def sort_sub_list(the_list, start, end):
orig_start = start
initial_start_second_list = (end - start)//2 + start + 1
list2_first_index = initial_start_second_list
new_list = []
while start < initial_start_second_list and list2_first_index <= end:
first1 = the_list[start]
first2 = the_list[list2_first_index]
if first1 > first2:
new_list.append(first2)
list2_first_index += 1
else:
new_list.append(first1)
start += 1
while start < initial_start_second_list:
new_list.append(the_list[start])
start += 1
while list2_first_index <= end:
new_list.append(the_list[list2_first_index])
list2_first_index += 1
# at this point, the total number each while statement ran is n
# now we have to do n again!
for i in new_list:
the_list[orig_start] = i
orig_start += 1
def merge_sort(the_list):
return _merge_sort((0, len(the_list) - 1), the_list)
if __name__ == '__main__':
for i in range(100):
n = 100
l = range(n)
random.shuffle(l)
merge_sort(l)
assert l == range(n)

Quicksort intermediate list printing Python

The prompt for this HackerRank problem has me stumped. It is essentially a quicksort implemention but as an exception you are required to print the intermediate (or semi-sorted) "original" array in its entirety each iteration.
My working code without printing intermediates. It works as expected.
def quicksort(array):
if len(array) > 1:
left = 0
right = len(array)-2
pivot = len(array)-1
while left <= right:
while array[left] < array[pivot]:
left +=1
while array[right] > array[pivot]:
right -= 1
if left <= right:
array[left], array[right] = array[right], array[left]
left += 1
right -=1
array[left], array[pivot] = array[pivot], array[left]
return quicksort(array[0:left]) + quicksort(array[left::])
else:
# return single element arrays
return array
And below is my attempt at implementing a way to print intermediates. I.e. I am trying to keep the indices separate instead of just passing the spliced list like the above example so that I will always have access to the full array in the first function parameter.
def quicksort2(array, start=0, end=None):
size = len(array[start:end])
if size > 1:
left = start
right = len(array[start:end])-2
pivot = len(array[start:end])-1
print("Print")
print("left: {}\nright: {}\npivot: {}".format(left, right, pivot))
while left <= right:
while array[left] < array[pivot]:
left +=1
while array[right] > array[pivot]:
right -= 1
if left <= right:
array[left], array[right] = array[right], array[left]
left += 1
right -=1
array[left], array[pivot] = array[pivot], array[left]
print(array)
print("the end is {}".format(end))
size = len(array[0:left]) # 3
return quicksort2(array, start=0, end=left) + quicksort2(array, start=left, end=left+(size-len(array)))
else:
# return single element arrays
return array
if __name__ == '__main__':
size = int(input()) # size is 7
arr = [int(i) for i in input().split()]
print(quicksort2(arr, start=0, end=size))
However, now the list are not fully sorted on the second half of the input list so I am sure it has something to do with the end keyword parameter that is passed at the bottom of the quicksort2 definition.
Figured out what I was doing wrong. I really needed to use the Lomuto Partitioning method in order to satisfy the requirements of the print statements.
Code for anyone searching for this in the future
def partition(array, lo, hi):
pivot_index = hi
pivot_value = array[pivot_index]
store_index = lo
for i in range(lo, hi):
if array[i] <= pivot_value:
array[i], array[store_index] = array[store_index], array[i]
store_index += 1
array[pivot_index], array[store_index] = array[store_index], array[pivot_index]
return store_index
def quicksort(array, lo, hi):
if lo < hi:
p = partition(array, lo, hi)
print(' '.join([str(i) for i in array]))
quicksort(array, lo, p-1)
quicksort(array, p+1, hi)
if __name__ == '__main__':
size = int(input())
ar = [int(i) for i in input().split()]
quicksort(ar, 0, size-1)

Categories