Quick Sort Algorithm using Haskell

Pi19404
August 4, 2013

Contents

Contents
Quick Sort Algorithm using Haskell
0.1 Introduction . . . . . . . . . . . 0.2 Quick Sort . . . . . . . . . . . . 0.2.1 Partioning the List . . 0.2.2 Selecting Random Pivot 0.2.3 Implementation . . . . . References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3
3 3 3 6 8 8

2|8

Quick Sort Algorithm using Haskell

Quick Sort Algorithm using Haskell
0.1 Introduction
In this article we will look at implementation of Quick Sort Algorithm using Haskell.

0.2 Quick Sort
Quick sort is a divide and conquer algorithm.Quick sort first divides a large list into two smaller sub lists.It then recursively sorts the sub lists. This is similar to merge sort. Input the algorithm is unsorted array,and output is the sorted array and possibly number of iterations. Let C be the output array of length (N ) Let us assume we have split the list into two parts A and B about a element of index p.This element is called the pivot element. All the elements to left of pivot element are less than pivot element and all the elements of list to right of pivot element are greater than pivot element. The sub lists A and B are sorted recursively. Thus quick sort operation consists of following steps 

 

choose pivot element partitioning the list Recursion

0.2.1 Partitioning the List
If we are allowed to choose additional array for storage,the partitioning operation can be performed by going over the list once that is O(N ). The aim of partitioning algorithm is to move all the elements which are less than pivot element to be placed to left of pivot element and all elements greater than pivot to the right.

3|8

Quick Sort Algorithm using Haskell Without loss of generality let us assume that first element of the list is choose as the pivot element. If any other element is chosen as pivot element,as initialization step replace the first element of the list with the pivot element and proceed with the present algorithm. To perform in place sorting we maintain 2 indexes i; j .The index i indicates the elements of the list that are below pivot element,from index i to j are elements above pivot element.The element above index j are elements that are yet to be analyzed. Initially the index i=-1 and j=-1 which is a trivial case.The index j is incremented Now compare the element at index i with index j .Let value of pivot element be represented as p.if p < a[j ] then we need to add a[i] .
5:

jj3 7 4 10 1 100 200 2 
; ; ; ; ; ; ;

>

5 : 3 7; 4; 10; 1; 100; 200; 2

jj

in the above example 5 > 3,however since i and j point to same index location no swapping is performed.The i and j indexes are simply incremented by 1 and updated index’s are marked Now the pivot element is compared with 3,in this case 4 > 3 ,j is incremented by 1, and 3 is swapped with 5,ie a[i] is swapped with a[j + 1]. next the element 5 is compared with 7.5 < 7,the current index of i is kept the same while j is incremented.
5 : 3 7; 4; 10; 1; 100; 200; 2

jj

   

>

5 : 3 7 4; 10; 1; 100; 200; 2

jj

next 5 is compared with 4.5 > 4 and i and j are 0 and 1 respectively. This elements 7 and 4 are swapped and i and j are incremented
5 : 3 7 4; 10; 1; 100; 200; 2

jj

>

5 : 34 7 10; 1; 100; 200; 2

jj

This process is repeated till j reaches the end of array,ie till no unobserved elements of array are remaining
5 : 3412; 710020010

These two sublists are recursively sorted and finally arrays are appended to get the result.
3 : 12; 47 :; 100; 200; 10

 

>

100 : 10200

However the above method is standard method described for partitioning used in quick sort algorithm.This method is useful if we are

4|8

Quick Sort Algorithm using Haskell required to perform in-place partitioning and it does not require additional memory storage. However functional language deals with immutable data type the list cannot be altered.in place operations are not allowed. We can perform transformation on the current list and return a new list. using above method of swapping and comparing proves to be more expensive. To partition a list of length N ,O(N ) comparisons are required. further each step swapping is performed which further requires N computations. and this step itself leads to O(N 2 ) computations. In which case above approach does not prove to be useful.A recursive method to partition the list is used. At each step the pivot element is compared to the head of the list and if true the element is appended to one list else it is appended to another list. Using a recursive method ,we simply perform a single comparison and single copy operations at each iteration loop.And partitioning is performed in O(N ) computations. These list are used as an accumulator.They are passed to input of the function and transformed list is expected at output of the function. The partitioning algorithm accepts the pivot element,the input list,and two accumulator sublists. The partitioning is performed recursively till all the elements of input list to be partitioned are exhausted. Let us consider a part_list1 algorithm. A stable sorting algorithm keeps the elements with same sorting key in order. They return the same output on the same input. In the present case if we have duplicate elements ,their order should be preserved. The part_list1 analyzes the list from head ,and new elements are appended to the tail of the list.Thus relative order of duplicate elements will always be reversed. This more over due to so simple way to add element to end of empty list. ie [] : 1 is not allowed while 1 : [] is allowed.Another solution would be to reverse the list.Thought it is again O(N ) additional computation. Also in program we must avoid computation of length of list which again takes O(N ) time. Using random pivot selection we can also get average case performance of O(N logN ).

5|8

Quick Sort Algorithm using Haskell

0.2.2 Selecting Random Pivot
The true strength of quick sort algorithm is random selection of pivot. If we select the first element of pivot and array is a sorted list it will perform O(N 2 ) computations,since at each iteration the size of smaller subproblem in (N   1).If the median is selected as pivot in each of the sublists,size of subproblem is (N=2) this gives us O (N logN ) performance. The random number generator is required by quick sort algorithm. In the standard method we remove the pivot element,con-cat the remaining array and then proceed with partitioning operations,however this requires copying of the array and again O(N ) additional operation. Rather than creating a new array,we maintain the index of the pivot element and during partitioning operations when we are performing the comparison operation the index of current element is checked against the pivot index and skipped during processing. This does not lead to additional copy of the array. Thus modified partition method is as below

-- using accumulator lists to partition a list for quick sort algorithm -- append elements less than pivot to as -- append elements greater than pivot to bs -- i is current index,to skip over pivot element -- l1 and l2 are length of array as and bs -- as are element less than or equal to pivot -- bs are elements greater than pivot part_list1 pivot pi [] as bs l1 l2 i= (as,bs,l1,l2,i) part_list1 pivot pi (y:ys) as bs l1 l2 i=case (i)==pi of flag|flag==True->part_list1 pivot pi ys as bs l1 l2 (i+1) |otherwise->f where f=case compare y pivot of LT->part_list1 pivot pi ys (y:as) bs (l1+1) l2 (i+1) EQ->part_list1 pivot pi ys (y:as) bs (l1+1) l2 (i+1) GT->part_list1 pivot pi ys as (y:bs) l1 (l2+1) (i+1)
In the above method while selecting the pivot element we need to know the length of the array.Rather than computing it every time.This is kept track of during the partitioning operation. Below is the method for basic quick sort algorithm

-- the main function to be called for -- partitioning the list for quick sort algorithm

6|8

Quick Sort Algorithm using Haskell

partition_list partition_list partition_list -- terminating

:: Ord a =>[a]->Int->StdGen->([a]) [] l g =([]) (f) l g = case length(f)<=2 of condition if sublists are sorted f1|f1==True -> (a1 ++ pivot:b1) -- sort the sublist recursively and then append |otherwise-> (c1 ++ pivot:d1) where -- val1 is pivot,val 2 is list to be partitioned (a1,b1,l1,l2,i)=part_list1 pivot pi f [] [] 0 0 0 --- partitioning subroutine called in case of sublists --- else list is appended and returned (c1)=partition_list a1 l1 g' (d1)=partition_list b1 l2 g'' (pivotLoc,g')=next g (pivotLoc',g'')=next g' pi=pivotLoc `mod` l pivot=(f)!!pi
Typically concatinating lists are expensive operations.In case of functional language it should be avoided.As used in partitioning method a method which uses accumulator arrays would be more beneficial. Let us consider the case that we have two lists an unsorted list and accumulator list which contains the sorted portion. At each iteration we first partition unsorted list into A and B. Then recursively sort B,let S be sorted version of B and C be input accumulator and P is pivot element The sorted array at this step is P+S+C and will be the new accumulator. The sorting routine is modified to accept the accumulator as input and outputs S+C as the sorted array . At the lowest element of recursion we append the higher element of B with C At the next highest level we append the next highest element with (B+C) and so on. Thereby adding to input accumulator at each step to produce the output list.

-- the quick sort function with accumulator partition_list1 :: Ord a =>[a]->Int->StdGen->[a]->Int->([a]) partition_list1 [] l g acc la=acc partition_list1 [x] 1 g acc la=x:acc -- adding pivot to sorted to get complete sorted list -- calling quick sort on unsorted list as and input accumulator

7|8

Quick Sort Algorithm using Haskell

-- as current sorted list partition_list1 f l g acc la= partition_list1 as l1 g' ([pivot] ++ sorted) (l2) --(sorted ++ [pivot] ++ as ++ [pivot] ++ bs ++ [pivot]) -where sorted=partition_list1 bs l2 g'' acc la -- partitioning the unsorted list (as,bs,l1,l2,i)=part_list1 pivot pi f [] [] 0 0 0 -- getting instance of random number generator (pivotLoc,g')=next g (pivotLoc',g'')=next g' -- index of pivot pi=pivotLoc `mod` l -- getting pivot element pivot=(f)!!pi

0.2.3 Implementation
Ill be implementing all the algorithms using haskel. Since recursion is basic mechanism to loop in Haskell ,it fits in naturally within recursive algorithmic structure of divide and conquer algorithms. The code can be found at https://github.com/pi19404/m19404/blob/ master/Algorithm/sort/sort.hs

8|8