Six different versions of QuickSort

Pre-prefix

Using English to write post is only for personal practice, I hope this will not bother you too much. Since you are one of us - the smartest and most creative programmers.

Prefix

This post is just used to record some personal experiments related to QuickSort including six versions of it here:

the first is the very basic but regular one using partition and recursion;

the second the re-arranged one merging the partition and the sorting in one single function to make it terse;

the third the stack-one using stack to avoid deep recursion eliminating functions invoking overhead;

the fourth is an optimized stack-version trying to handle the smaller blocks first by pushing the bigger blocks into the stack first to decrease the depth of the stack;

the fifth is another recursive version handling the small blocks by insertion sort to further improve performance;

the sixth and also the last is trying to select a better partitioning value to avoid the worst uneven partitioning situation - partitioning value is the first or last value. There are different features in those versions, some of them might be better by merging several of them, the others might be just useful in most situations. A simple shell script is also enclosed to measure the performance for each of them. Might this be helpful to others too.

Version 1.1 Now let's take a look at the first QuickSort version  where, personally speaking, the partitioning part is pretty clear and terse.

int partition(int *nums, int l ,int r)
{
    int i = l;
    int j = r;
    int v = nums[(i+j)/2];
    while(i <= j)
    {
        while(nums[i] < v) i++;
        while(nums[j] > v) j--;
        if(i <= j)
        {
            swap(nums + i, nums + j);
            i++;
            j--; 
        } 
    }
    return i; 
}

void sort(int *nums, int l, int r)
{
    int i = partition(nums, l, r);
    if(l < i - 1) sort(nums, l, i - 1);
    if(r > i) sort(nums, i, r);
}

Version 1.2 is quite the same as the previous two versions but this is also the version where I melt the partitioning and sorting part into one single function sort to make it more terse.

void sort(int *a, int l, int r)
{
    int v = a[(l+r)/2];
    int i = l, j = r;
    while(i <= j)
    {
        while(a[i] < v)
            i++;
        while(a[j] > v)
            j--;
        if(i <= j)
        {
            swap(a+i, a+j);
            i++;
            j--;
        }
    }
    if(l < j)
        sort(a, l, j);
    if(i < r)
        sort(a, i, r);
}

Version 1.3 To try another regular type of QuickSort - using stack to avoid too deep recursive function invoking process. So there will a stack used to achieve that goal.

void sort(int *a, int l, int r)
{
    stackInit();
    push(l, r);
    int i, j, v;
    int l0, r0;
    while(!isEmpty())
    {
        r0 = j = pop();
        l0 = i = pop();
        v = a[(i+j)/2];
        while(i <= j)
        {
            while(a[i] < v)
                i++;
            while(a[j] > v)
                j--;
            if(i <= j)
            {
                swap(a+i, a+j);
                i++;
                j--;
            }
        }
        if(l0 < j)
            push(l0, j);
        if(r0 > i)
            push(i, r0);
    }
}

Then there is the stack part:

int STACK[SIZE];
int INDEX;

void stackInit()
{
    INDEX = -1;
}

int pop()
{
    if(INDEX < 0)
        return -1;
    return STACK[INDEX--];
}

void push(int l, int r)
{
    if(INDEX >= SIZE - 2)
        return;
    STACK[++INDEX] = l;
    STACK[++INDEX] = r;
}

bool isEmpty()
{
    if(INDEX >= 0)
        return false;
    return true;
}

Version 1.4 Another stack version trying to make the bigger blocks pushed into the stack before the smaller ones to decrease the depth of the stack.

void sort(int *a, int l, int r)
{
    stackInit();
    int i, j, v;
    int l0, r0;
    push(l, r);
    while(!isEmpty())
    {
        r0 = j = pop();
        l0 = i = pop();
        v = a[(i+j)/2];
        while(i <= j)
        {
            while(a[i] < v)
                i++;
            while(a[j] > v)
                j--;
            if(i <= j)
            {
                swap(a+i, a+j);
                i++;
                j--;
            }
        }
        if(j-l0 > r0-i)
        {
            if(j > l0)
                push(l0, j);
            if(i < r0)
                push(i, r0);
        }
        else
        {
            if(i < r0)
                push(i, r0);
            if(j > l0)
                push(l0, j);
        }
    }
}

Version 1.5 When the section of the array is small, the QuickSort might be slower than regular sorting method like insertion sort so let's try to sort the smaller parts of the array first for the QuickSort.

void sort(int *a, int l, int r)
{
    int i, j, v;
    i = l, j = r;
    v = a[(i + j)/2];
    if(j - i < 20)
    {
        insertionSort(a, i, j);
        return;
    }
    while(i <= j)
    {
        while(a[i] < v)
            i++;
        while(a[j] > v)
            j--;
        if(i <= j)
        {
            swap(a + i, a + j);
            i++;
            j--;
        }
    }
    if(j > l)
        sort(a, l, j);
    if(i < r)
        sort(a, i, r);
}

The corresponding insertionSort code is as follows:

void insertionSort(int* nums, int l, int r)
{
    //Via bubbling, you can also make the minimal in the first position;
    //All the rest elements will be less than the first;
    int min=0;
    for(int i = l; i <= r; i++)
    {
        if(nums[i] < nums[min])
            min = i;
    }
    swap(nums, nums + min);
    int j = 0;
    for(int i = l + 1; i <= r; i++)
    {
        int j = i - 1;
        int tmp = nums[i];
        while(nums[j] > tmp)
        {
            nums[j+1] = nums[j];
            j--;
        }
        nums[j + 1] = tmp;
    }
}

Version 1.6 One more thing we should easily ignore is that the first selected partitioning value might not be ideal which could make the whole sorting process pretty tricky - partitioning the array quite uneven. So we should try to avoid this special situation by selecting a better partitioning value in a comparably simple way.

void sort(int *a, int l, int r)
{
    int i = l, j = r;
    if(j - i < 20)
    {
        insertionSort(a, i, j);
        return;
    }
    int mid = (i + j) / 2;
    swap(a + i, min(a + i, min(a + mid, a + j)));
    swap(a + j, max(a + j, a + mid));
    int v = a[mid];
    while(i <= j)
    {
        while(a[i] < v)
            i++;
        while(a[j] > v)
            j--;
        if(i <= j)
        {
            swap(a + i, a + j);
            i++;
            j--;
        }
    }
    if(j > l)
        sort(a, l, j);
    if(i < r)
        sort(a, i, r);
}

The related derectives are here while the function swap is just posted in the previous version.

#define minP(pa, pb) (*(pa) > *(pb) ? (pb) : (pa))
#define maxP(pa, pb) (*(pa) > *(pb) ? (pa) : (pb))

I am currently using CentOS 7, a linux system so there is also a simple shell script used to check the performance of the whole versions of QuickSort. With the swift0 pointing to the Version 1.0, all the following swift* will be pointing to the following versions in sequence.

#!/bin/bash
###########################
#Author: LHearen
#E-mail: [email protected]
###########################
#Redirect the stdout to stderr
#Make sure when running './time.sh >tmp'
#The shell will only prompt the brief hints.
#Cooperated with function 'checkAscending' in utils.c and command './time.sh >tmp'
time ./swift1
echo 'time ./swift1' >&2
echo '***************' >&2
time ./swift2
echo 'time ./swift2' >&2
echo '***************' >&2
time ./swift3
echo 'time ./swift3' >&2
echo '***************' >&2
time ./swift4
echo 'time ./swift4' >&2
echo '***************' >&2
time ./swift5
echo 'time ./swift5' >&2
echo '***************' >&2
time ./swift6
echo 'time ./swift6' >&2
echo '***************' >&2

There are some related code to make different versions of QuickSort run smoothly.

File utils.h

#ifndef UTILS_H
#define UTILS_H
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<stdbool.h>

#define SIZE 2000000
#define MAX 10000000

#define minP(pa, pb) (*(pa) > *(pb) ? (pb) : (pa))
#define maxP(pa, pb) (*(pa) > *(pb) ? (pa) : (pb))

extern int STACK[SIZE];
extern int INDEX;

void stackInit();
int pop();
void push(int l, int r);
bool isEmpty();

void randomIntArray(int* array, int size, int low, int high);
void printArray(int *nums, int size);
void swap(int*p, int*q);
void checkAscending(int *nums, int size);
void insertionSort(int* nums, int l, int r);
#endif

File utils.c

#include"utils.h"

void randomIntArray(int* array, int size, int low, int high)
{
    srand(time(NULL));
    for(int i = 0; i < size; i++)
    {
        array[i] = rand()%(high-low) + low;
    }
}

void printArray(int *nums, int size)
{
    for(int i = 0; i < size; i++)
        printf("%d, ", nums[i]);
}

void swap(int*p, int*q)
{
    int tmp = *p;
    *p = *q;
    *q = tmp;
}

void checkAscending(int *nums, int size)
{
    for(int i = 0; i < size - 1; i++)
        if(nums[i] > nums[i + 1])
        {
            //Make sure the output will be directed to stderr; 
            //Cooperated with shell script time.sh and command './time.sh >tmp'
            perror("Sorting failed!\n");
            printf("Sorting failed!\n");
            return;
        }
    perror("Sorted successfully!\n");
    printf("Sorted successfully!\n");
}

void insertionSort(int* nums, int l, int r)
{
    //Via bubbling, you can also make the minimal in the first position;
    //All the rest elements will be less than the first;
    int min=l;
    for(int i = l; i <= r; i++)
    {
        if(nums[i] < nums[min])
            min = i;
    }
    swap(nums + l, nums + min);
    int j = 0;
    for(int i = l + 1; i <= r; i++)
    {
        int j = i - 1;
        int tmp = nums[i];
        while(nums[j] > tmp)
        {
            nums[j+1] = nums[j];
            j--;
        }
        nums[j + 1] = tmp;
    }
}

File stack.c

#include"utils.h"

int STACK[SIZE];
int INDEX;

void stackInit()
{
    INDEX = -1;
}

int pop()
{
    if(INDEX < 0)
        return -1;
    return STACK[INDEX--];
}

void push(int l, int r)
{
    if(INDEX >= SIZE - 2)
        return;
    STACK[++INDEX] = l;
    STACK[++INDEX] = r;
}

bool isEmpty()
{
    if(INDEX >= 0)
        return false;
    return true;
}

File swift1.c

#include"utils.h"
int partition(int *nums, int l ,int r)
{
    int i = l;
    int j = r;
    int v = nums[(i+j)/2];
    while(i <= j)
    {
        while(nums[i] < v) i++;
        while(nums[j] > v)
            j--;
        if(i <= j)
        {
            swap(nums + i, nums + j);
            i++;
            j--; 
        } 
    }
    return i; 
}

void sort(int *nums, int l, int r)
{
    int i = partition(nums, l, r);
    if(l < i - 1)
        sort(nums, l, i - 1);
    if(r > i)
        sort(nums, i, r);
}

void main()
{
    int numbers[SIZE];
    randomIntArray(numbers, SIZE, 0, MAX);
    printArray(numbers, SIZE);
    checkAscending(numbers, SIZE);
    printf("After sorting:\n***********************\n");
    sort(numbers, 0, SIZE - 1);
    printArray(numbers, SIZE);
    checkAscending(numbers, SIZE);
}

Summary:

Now all you have to do is simply replace the sorting part with different versions of QuickSort discussed and rename the file and then you can easily use the shell scripts to time all versions of QuickSort by command './time.sh >tmp' which will prompt the time and correspoinding versions of QuickSort. Have a nice day!

P.S. Some configuration and testing result

description: Desktop Computer
product: ThinkCentre M8300T (To be filled by O.E.M.)
vendor: LENOVO
version: Lenovo Product
serial: NA17134507
width: 64 bits
 
Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz
              total        used        free      shared  buff/cache   available
Mem:           7.5G        1.2G        3.4G        511M        2.9G        5.5G
Swap:          3.8G          0B        3.8G
 
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              8192K

And the results of all versions - testing set is a 2,000,000 random array integers ranging from 0 to 10,000,000 exclusive:

./time.sh > tmp
Sorting failed!
: Success
Sorted successfully!
: Success

real    0m0.736s
user    0m0.720s
sys    0m0.016s
time ./swift1
***************
Sorting failed!
: Success
Sorted successfully!
: Success

real    0m0.656s
user    0m0.636s
sys    0m0.020s
time ./swift2
***************
Sorting failed!
: Success
Sorted successfully!
: Success

real    0m0.664s
user    0m0.645s
sys    0m0.019s
time ./swift3
***************
Sorting failed!
: Success
Sorted successfully!
: Success

real    0m0.670s
user    0m0.650s
sys    0m0.021s
time ./swift4
***************
Sorting failed!
: Success
Sorted successfully!
: Success

real    0m0.617s
user    0m0.597s
sys    0m0.020s
time ./swift5
***************
Sorting failed!
: Success
Sorted successfully!
: Success

real    0m0.625s
user    0m0.606s
sys    0m0.019s
time ./swift6
***************

P.S. P.S. If there is anything wrong - the contents, the sentences and even the styles, please do not hesitate to inform me in the reply, so many thanks in advance!


你可能感兴趣的:(version,Quicksort,performance,complete)