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.
Related
I am posting for help on a Quicksort median of three problem. I am required to implement a partition that uses a pivot that is the median of three elements in the input list (start, middle, end) while accounting for some edges cases.
In brief, I need to implement a method choose_median() that returns the index of the median element (the chosen pivot); print the index of the pivot chosen at the previous step; create a partition()method so that it can work with the chosen pivot; print the list after the first partition.
I've been having trouble with this case for even lists:
"Note that if the size of a list is even, there are two ways to choose a median element. To avoid ambiguity, choose an element with a smaller index as a median in this case."
This is deceptively hard, I've been stumped for a while. Especially on lists with length 2 or less.
I have a method for the partition that works for cases if given the appropriate pivot:
def partition(arr, pivot):
less, equal, greater = [], [], []
for val in arr:
if val < pivot: less.append(val)
if val == pivot: equal.append(val)
if val > pivot: greater.append(val)
return less+equal+greater, pivot
I could also change this function to work with the pivot too.
def partition(lst, pivot, start, end):
j = start
for i in range(start + 1, end + 1):
if lst[i] <= lst[start]:
j += 1
lst[i], lst[j] = lst[j], lst[i]
lst[start], lst[j] = lst[j], lst[start]
return j
Choosing the median with odd number lists are straightforward.
Also, I've seen/know how to implement the implementations here:
Python: Quicksort with median of three
Any help would be appreciated.
You seem to make this harder than it really is. First of all, I think you confused your terminology of "median value" and "middle element".
The middle element of the list, rounding down, is simply lst[(len(lst) - 1)//2]: your index is the list length, minus 1, integer divide by 2.
Therefore, your pivot selection is easy: take the three indicated elements, sort them, and return the middle element.
def choose_pivot(lst):
return sorted( [lst[0],
lst[-1],
lst[(len(lst)-1) //2]
])[1]
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 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 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]
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.