QuickSort
-
futures
- Divide and conquer paradigm
- Sorts “in place” (就地排序)
- Very practical (with tuning)
-
Divide and Conquer
- Divide:Partition array into 2 subarrays around pivot x such that elems in lower subarrays <=x <= elems in upper subarray
Ex. array A = (6,10,13,5,8,3,2,11),choose the first element 6 as the pivot.Then we want to partition A into 2 arrays such that A has the form like follows(Here x=6,where the left and the right can regard as 2 subarrays):
- Conquer: Recursively sort 2 subarrays
- Combine:This is trivial for doing almostly nothing.
- Divide:Partition array into 2 subarrays around pivot x such that elems in lower subarrays <=x <= elems in upper subarray
-
Key Point:Linear-time Θ(n) for partition subroutine.
- Pseudocode for partition subroutine.
Here, the subroutine return i as an partition for the next recurrence.
- Pseudocode for partition subroutine.
partiton(A,p,q)
x ← A[p]
i ← p
for j ← p+1 to q
do if A[j] <= x
then i ← i+1
exch A[i] ←→ A[j]
exch A[p] ←→ A[i]
return i
- Pseudocode for recusive algorithm
quicksort(A,p,q)
if p < q
then r ← partition(A,p,q)
quicksort(A,p,r-1)
quicksort(A,r+1,q)
initCall()
quicksort(A,1,n)
Attention: This basic algorithm is suitable for processing data without duplicates, and those with duplicates can use Hoare algorithm.
Ex .
partiton(A,p,q)
x ← A[p]
i ← p
for j ← p+1 to q
do if A[j] <= x
then i ← i+1
exch A[i] ←→ A[j]
exch A[p] ←→ A[i]
return i
initial array A: 6 10 13 5 8 3 2 11(choose 6 as pivot)
i j->move j to find elem<pivot(We find 5,then i++,swap(A[i],A[j])
We have: 6 5 13 10 8 3 2 11
i j->move j to find elem<pivot(We find 3,then i++,swap(A[i],A[j])
We have: 6 5 3 10 8 13 2 11
i j>move j to find elem<pivot(We find 2,then i++,swap(A[i],A[j])
We have: 6 5 3 2 8 13 10 11
i j >move j to find elem<pivot(None)
Finally,We exchange(A[p],A[i])
We have: 2 5 3 6 8 13 10 11
It is worth mentioning that there’s something wrong what the prefessor said in the example,the i should self-add before each swap rather than swap finished.
- Java code
Here I made some comparison between disorder input and order input ,which shows that disorder input case has better time consuming.
package nov21;
import java.util.Arrays;
import java.util.Random;
public class QuickSortTester {
public int[] list;
public QuickSortTester(int num)
{
list = new int[num];
for(int i=0;i<num;i++)
{
list[i] = (int)(Math.random()*20000+2);
}
}
public static void main(String[] args) {
int[] a = new QuickSortTester(10000).list;
int[] b = disrupt(a);// 打乱后的数组
long t1 = System.currentTimeMillis();
quickSort(a,0,a.length-1);
// System.out.println(Arrays.toString(a));
long t2 = System.currentTimeMillis();
System.out.println("Running Time: "+(t2-t1)+" ms");
long t3 = System.currentTimeMillis();
quickSort(b,0,b.length-1);
long t4 = System.currentTimeMillis();
System.out.println("Disrupt-Running Time: "+(t4-t3)+" ms");
// 用已经排好序的数组a
long t5 = System.currentTimeMillis();
quickSort(a,0,a.length-1);
// System.out.println(Arrays.toString(a));
long t6 = System.currentTimeMillis();
System.out.println("Sorted_Running Time: "+(t6-t5)+" ms");
int[] c = disrupt(a);// 打乱后的数组
long t7 = System.currentTimeMillis();
quickSort(c,0,c.length-1);
// System.out.println(Arrays.toString(a));
long t8 = System.currentTimeMillis();
System.out.println("Disrupted-Sorted_Running Time: "+(t8-t7)+" ms");
}
public static void quickSort(int[] a,int p,int q)
{
if(p<q)
{
int r = partition(a,p,q);
quickSort(a,p,r-1);
quickSort(a,r+1,q);
}
}
public static int[] disrupt(int arr[]) {
int arrTemp[] = new int[arr.length];
int size = arr.length-1;
Random rd = new Random();
for(int i = 0 ; i < arr.length ; i++) {
int rand = rd.nextInt(size+1);
arrTemp[i] = arr[rand];
arr[rand] = arr[size];
size--;
}
return arrTemp;
}
public static int partition(int[] a,int p,int q)
{
int pivot = a[p];
int i = p;
for(int j=p+1;j<q;j++)
{
if(a[j]<=pivot)
{
i++;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
int temp = a[i];
a[i] = pivot;
a[p] = temp;
return i;
}
}
-
Runing time
Analysis-assume all elems are distinct.-
T(n) = worst-case time when
- input sorted or reverse sorted
- one side of partition has no elems
then T(n) = T(0)+T(n-1)+Θ(n) = Θ(n ^2) (Arith series,like insertion sort).
We can alse use recursion-tree :
-
T(n) = best-case time when
- we’are lucky to partition splits the arrays always n/2:n/2
then T(n) = T(n)=2T(n/2)+Θ(n)=Θ(nlgn) (master method in case 2)
- we’are lucky to partition splits the arrays always n/2:n/2
-
Suppose we’are able to partition splits the arrays always n/10:9n/10
T(n) = T(n/10)+T(9n/10)+Θ(n)
then we can use recursion-tree to konw
Conclusion:cnlog(10) n + Θ(n) <= T(n) <= cnlog(10/9) n + Θ(n)
-
-
Suppose we can alternate lucky,unlucky case :
L(n)=2U(n/2)+Θ(n)(lucky)
U(n)=L(n-1)+ Θ(n)(unlucky)
Then L(n)=2(L(n/2-1))+Θ(n/2))+Θ(n)
=2L(n/2-1)+Θ(n)
=Θ(nlgn)
Randomized Algorithm for Quick-Sort
advantages
- running time is independent of input ordering.
- No assumption about input distribution.
- No specific input elicit worst-case behavior.
- worst-case determined by random number generator.
- Idea: randomly choose pivot use a random variant then we can get E[T(n)] is Θ(nlogn) with proof.
The detailed proof is as follows:
Sorry that my note is like draft.