I'm trying to implement quicksort in python. However, my code doesn't properly sort (not quite). For example, on the input array [5,3,4,2,7,6,1], my code outputs [1,2,3,5,4,6,7]. So, the end result interposes the 4 and 5. I admit I am a bit rusty on python as I've been studying ML (and was fairly new to python before that). I'm aware of other python implementations of quicksort, and other similar questions on Stack Overflow about python and quicksort, but I am trying to understand what is wrong with this chunk of code that I wrote myself:
#still broken 'quicksort'
def partition(array):
pivot = array[0]
i = 1
for j in range(i, len(array)):
if array[j] < array[i]:
temp = array[i]
array[i] = array[j]
array[j] = temp
i += 1
array[0] = array[i]
array[i] = pivot
return array[0:(i)], pivot, array[(i+1):(len(array))]
def quick_sort(array):
if len(array) <= 1: #if i change this to if len(array) == 1 i get an index out of bound error
return array
low, pivot, high = partition(array)
#quick_sort(low)
#quick_sort(high)
return quick_sort(low) + [pivot] + quick_sort(high)
array = [5,3,4,2,7,6,1]
print quick_sort(array)
# prints [1,2,3,5,4,6,7]
I'm a little confused about what the algorithm's connection to quicksort is. In quicksort, you typically compare all entries against a pivot, so you get a lower and higher group; the quick_sort function clearly expects your partition function to do this.
However, in the partition function, you never compare anything against the value you name pivot. All comparisons are between index i and j, where j is incremented by the for loop and i is incremented if an item was found out of order. Those comparisons include checking an item against itself. That algorithm is more like a selection sort with a complexity slightly worse than a bubble sort. So you get items bubbling left as long as there are enough items to the left of them, with the first item finally dumped after where the last moved item went; since it was never compared against anything, we know this must be out of order if there are items left of it, simply because it replaced an item that was in order.
Thinking a little more about it, the items are only partially ordered, since you do not return to an item once it has been swapped to the left, and it was only checked against the item it replaced (now found to have been out of order). I think it is easier to write the intended function without index wrangling:
def partition(inlist):
i=iter(inlist)
pivot=i.next()
low,high=[],[]
for item in i:
if item<pivot:
low.append(item)
else:
high.append(item)
return low,pivot,high
You might find these reference implementations helpful while trying to understand your own.
Returning a new list:
def qsort(array):
if len(array) < 2:
return array
head, *tail = array
less = qsort([i for i in tail if i < head])
more = qsort([i for i in tail if i >= head])
return less + [head] + more
Sorting a list in place:
def quicksort(array):
_quicksort(array, 0, len(array) - 1)
def _quicksort(array, start, stop):
if stop - start > 0:
pivot, left, right = array[start], start, stop
while left <= right:
while array[left] < pivot:
left += 1
while array[right] > pivot:
right -= 1
if left <= right:
array[left], array[right] = array[right], array[left]
left += 1
right -= 1
_quicksort(array, start, right)
_quicksort(array, left, stop)
Generating sorted items from an iterable:
def qsort(sequence):
iterator = iter(sequence)
try:
head = next(iterator)
except StopIteration:
pass
else:
try:
tail, more = chain(next(iterator), iterator), []
yield from qsort(split(head, tail, more))
yield head
yield from qsort(more)
except StopIteration:
yield head
def chain(head, iterator):
yield head
yield from iterator
def split(head, tail, more):
for item in tail:
if item < head:
yield item
else:
more.append(item)
If pivot ends up needing to stay in the initial position (b/c it is the lowest value), you swap it with some other element anyway.
Read the Fine Manual :
Quick sort explanation and python implementation :
http://interactivepython.org/courselib/static/pythonds/SortSearch/TheQuickSort.html
Sorry, this should be a comment, but it has too complicated structure for a comment.
See what happens for array being [7, 8]:
pivot = 7
i = 1
for loop does nothing
array[0] becomes array[i] which is 8
array[i] becomes pivot which is 7
you return array[0:1] and pivot, which are [8, 7] and 7 (the third subexpression ignored)...
If you explicitly include the returned pivot in concatenation, you should skip it in the array returned.
okay i "fixed" it, at least on the one input i've tried it on (and idk why... python issues)
def partition(array):
pivot = array[0]
i = 1
for j in range(i, len(array)):
if array[j] < pivot:
temp = array[i]
array[i] = array[j]
array[j] = temp
i += 1
array[0] = array[i-1]
array[i-1] = pivot
return array[0:i-1], pivot, array[i:(len(array))]
def quick_sort(array):
if len(array) <= 1:
return array
low, pivot, high = partition(array)
#quick_sort (low)
#quick_sort (high)
return quick_sort (low) + [pivot] + quick_sort (high)
array = [5,3,4,2,7,6,1]
print quick_sort(array)
# prints [1,2,3,4,5,6,7]
Related
I want to find the majority element from a list using divide & conquer algorithm.
I saw this code on Leetcode with this solution:
class Solution:
def majorityElement(self, nums, lo=0, hi=None):
def majority_element_rec(lo, hi):
# base case; the only element in an array of size 1 is the majority
# element.
if lo == hi:
return nums[lo]
# recurse on left and right halves of this slice.
mid = (hi-lo)//2 + lo
left = majority_element_rec(lo, mid)
right = majority_element_rec(mid+1, hi)
# if the two halves agree on the majority element, return it.
if left == right:
return left
# otherwise, count each element and return the "winner".
left_count = sum(1 for i in range(lo, hi+1) if nums[i] == left)
right_count = sum(1 for i in range(lo, hi+1) if nums[i] == right)
return left if left_count > right_count else right
return majority_element_rec(0, len(nums)-1)
when there is a majority element, the result is right but when there is not such an element, it returns the wrong result.
I tried to change the return statement to:
if left_count > right_count:
return left
elif left_count < right_count:
return right
else:
return -1
so it returns -1 when there is no right answer.
When the input is [1,2,1,3] the result is -1(right answer) but when the input is [1,2,3,3] the output is 3 which is wrong.
It seems that everyone use this solution but it isn't working. Any ideas about how to fix it?
TIA
I think the recursive step is OK since if there is a majority element, it must be the majority element of at least one of the halves of the array, and the recursive step will find it. If there is no majority element the recursive algorithm will still return a (wrong) candidate.
If you want the program to detect if the element is indeed the majority element,
I would just change the final step.
def majorityElement(self, nums):
...
candidate = majority_element_rec(0, len(nums)-1)
if nums.count(candidate) > len(nums)//2:
return count
else:
return -1
Alternatively the recursive step could perform the same check whether the found candidate is indeed a majority element by replacing the last line:
return left if left_count > right_count else right
with
majority = ((hi - lo) + 1) / 2
if left_count > majority:
return left
if right_count > majority:
return right
return -1
This should still work because the majority element if it exists must be the majority element in one half of the array recursively and must thus still propagate.
I know that in Python, variables are passed by giving a copy of the reference to the object. But I do not understand why in the following piece of code I wrote, the function Partition does not change the elements of arr.
def Partition(arr, lo, hi):
pivot = arr[lo]
i = lo
j = hi
while(True):
while(arr[i] < pivot):
i += 1
if i == hi: break
while(arr[j] > pivot):
j -= 1
if j == lo: break
if i >= j : break #check if ptrs cross
arr[i], arr[j] = arr[j], arr[i]
#swap lo and j
arr[lo], arr[j] = arr[j], arr[lo]
return j
def Sort(arr, start, end):
if (end <= start): return
right = Partition(arr, start, end)
Sort(arr, start, right-1)
Sort(arr, right+1, end)
Your Partition function has a logic problem.
If you follow it with a debugger, you will see it always gets the array back to its initial state before returning. The array would actually be seen as modified, the problem is that after fiddling with it for a while, it gets back to exactly how it was when entering the function.
Do you use debugging tools? If not, start doing so now.
If so, put a breakpoint on the return j statement, and examine the array, you will see what I mean.
You are trying to implement a Hoare partitioning, right?
I think you got it somewhat mixed up. The issue is because you're comparing against the pivot before the first loop iteration, you end up comparing the elements you just swapped again.
This seems to be a bug:
Assume arr = [1,3,4,7,5,8], lo=3, hi=6
def Partition(arr, lo, hi):
pivot = arr[lo] <- this is arr[3] = 7
i = lo <- i = 3
j = hi
while(True):
while(arr[i] < pivot): <- arr[3] = 7 so condition fails hence no swap
I have a quicksort program here, but there seems to be a problem with the result. I think there must have been some issue in the areas highlighted below when referencing some values. Any suggestions?
#where l represents low, h represents high
def quick(arr,l,h):
#is this the correct array for quicksorting?
if len(x[l:h]) > 1:
#r is pivot POSITION
r = h
#R is pivot ELEMENT
R = arr[r]
i = l-1
for a in range(l,r+1):
if arr[a] <= arr[r]:
i+=1
arr[i], arr[a] = arr[a], arr[i]
#should I take these values? Note that I have repeated elements below, which is what I want to deal with
quick(arr,l,arr.index(R)-1)
quick(arr,arr.index(R)+arr.count(R),h)
x = [6,4,2,1,7,8,5,3]
quick(x,0,len(x)-1)
print(x)
Please check this. I think you find your answer.
def partition(array, begin, end):
pivot = begin
for i in xrange(begin+1, end+1):
if array[i] <= array[begin]:
pivot += 1
array[i], array[pivot] = array[pivot], array[i]
array[pivot], array[begin] = array[begin], array[pivot]
return pivot
def quicksort(array, begin=0, end=None):
if end is None:
end = len(array) - 1
if begin >= end:
return
pivot = partition(array, begin, end)
quicksort(array, begin, pivot-1)
quicksort(array, pivot+1, end)
array = [6,4,2,1,7,8,5,3]
quicksort(array)
print (array)
#should I take these values? Note that I have repeated elements below, which is what I want to deal with
quick(arr,l,arr.index(R)-1)
quick(arr,arr.index(R)+arr.count(R),h)
You seem to be assuming that the values equal to the pivot element are already consecutive. This assumption is probably wrong for your current implementation. Test it e.g. by outputting the full list before recursing.
To make the assumption true, partition into three instead of just two groups, as described at Wikipedia.
I have two days trying to figure out why my quicksort function returns all but two elements sorted in correct order.
With the Input
quicksort([0,7,3,11,23,87,999,1023,12,713,5,6,9])
Outputs
[0, 6, 3, 5, 7, 9, 11, 12, 23, 87, 713, 999, 1023]
What do you think is wrong with the function?
def quicksort(array):
#Base case
if len(array) <= 1:
return array
#Choose pivot always at the left part and partition array around it
Pivot = partition(array,0,len(array));
#Add values for left and righ arrays
left = array[:Pivot]
right = array[Pivot+1:]
#Add recursive calls
left = quicksort(left)
right = quicksort(right)
#Append Pivot at the end of left array
left.append(array[Pivot])
return left+right
For matters of completion I'm adding the partition function but I'm almost sure the issue is not there.
def partition(array,left,right):
#If pivot is not on leftmost, swap to make it leftmost
Pivot = array[left]
i = left+1
for j in range(left+1,right):
if array[j] < Pivot:
#Swap array[j] and array[i]
array[j], array[i] = array[i], array[j]
i += 1;
#Swap pivot and A[i-1]
array[left], array[i-1] = array[i-1], array[left]
return left
Your partition function always returns the left argument, as it was supplied without changing it. I think you intend to return the final position of the pivot element.
Just for the sake of completion, making use of pythons comprehension lists, makes the code much simpler to implement and read.
def qs(l):
if len(l) < 2:
return l
pivot = l.pop()
left = [x for x in l if x < pivot]
right = [x for x in l if x >= pivot]
return qs(left) + [pivot] + qs(right)
It reads pretty much like the pseudo-code found in text books.
Lists of size 1 or 0 are sorted by definition
Choose a Pivot. In this case it's the last element
Create two partitions so that one has all the elements smaller than the pivot and the other has the elements greater then or equal to the pivot
Recursively call quicksort on both partitions and append the results with the pivot in the middle
I'm learning the quicksort algorithm, but for some reason, the output of this python implementation is just partially sorted, and I get the 'maximum recursion depth reached' for larger inputs. I've been banging my head against this for the last couple of days and I know it's probably something really stupid, but I can't seem to figure it out, so I'll appreciate any help.
def ChoosePivot(list):
return list[0]
def Partition(A,left,right):
p = ChoosePivot(A)
i = left + 1
for j in range(left + 1,right + 1): #upto right + 1 because of range()
if A[j] < p:
A[j], A[i] = A[i], A[j] #swap
i = i + 1
A[left], A[i - 1] = A[i-1], A[left] #swap
return i - 1
def QuickSort(list,left, right):
if len(list) == 1: return
if left < right:
pivot = Partition(list,left,right)
QuickSort(list,left, pivot - 1)
QuickSort(list,pivot + 1, right)
return list[:pivot] + [list[pivot]] + list[pivot+1:]
sample_array = [39,2,41,95,44,8,7,6,9,10,34,56,75,100]
print "Unsorted list: "
print sample_array
sample_array = QuickSort(sample_array,0,len(sample_array)-1)
print "Sorted list:"
print sample_array
Not entirley sure this is the issue, but you are chosing pivot wrongly:
def ChoosePivot(list):
return list[0]
def Partition(A,left,right):
p = ChoosePivot(A)
....
You are always taking the head of the original list, and not the head of the modified list.
Assume at some point you reduced the range to left=5,right=10 - you chose list[0] as the pivot - that can't be good.
As a result, in each iteration where left>0 you ignore the first element in the list, and "miss" it - which can explain the partial sorting
def ChoosePivot(list):
return list[0]
As amit said, this is wrong. You want p = A[left]. However, there is another issue:
if A[j] < p:
A[j], A[i] = A[i], A[j] #swap
i = i + 1
The pivot index should only be incremented when you swap. Indent i = i + 1 to the same depth as the swap, as part of the if statement.
Bonus question: Why are you partitioning twice?
also last swap;
A[left], A[i - 1] = A[i-1], A[left] #swap
should be done with pivot.
Besides that Quicksort works inplace. So you don't need following return;
return list[:pivot] + [list[pivot]] + list[pivot+1:]
Not exactly an answer to your question, but I believe it's still of most relevance.
Choosing a pivot always on the same position when implementing quicksort is a flaw on the algorithm. One can generate a sequence of numbers that makes your algorithm run in O(n^2) time, and absolute run time probably worse than bubblesort.
In your algorithm, choosing the leftmost item makes the algorithm run in worst-case time when the array is already sorted or nearly sorted.
The choice of the pivot should be performed randomly to avoid this issue.
Check the algorithms' Implementation issues in Wikipedia: http://en.wikipedia.org/wiki/Quicksort#Implementation_issues
Actually, check the hole article. It IS worth your time.