Home > Software engineering >  Generic Quicksort in C
Generic Quicksort in C

Time:04-24

Can anyone tell me what am I doing wrong in this generic quicksort code following this pseudocode Quicksort & Partition, the algorithm works, because I have already done it with integers only without the compare function by passing an int array to the quicksort and partition functions, but I have tried to make it work for both int and strings. In this code I have tested only the int values, but the code doesn't work, the output is the initial value of the array, it's the same exact thing for the strings I get the same initial array as an output. I have commented the string part because they get sorted the same way as the integers. This is the integer code that works Integer working code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
//prototipi delle funzioni
typedef int (*compare_function)(const void *, const void *); 
 
void generic_quicksort(void *v, int i, int f, size_t size, compare_function compare);
void generic_swap(void *a, void *b, size_t size);
int generic_partition(void *v, int i, int f, size_t size, compare_function compare);
 
void print_int_array(const int *array, size_t len) { 
    size_t i;
 
    for (i = 0; i < len; i  ) 
        printf("%d | ", array[i]);
 
    putchar('\n');
}

//funzione di confronto
int compare_int(const void *, const void *); 
 
int compare_str(const void *a, const void *b) { 
    const char **ia = (const char **)a;
    const char **ib = (const char **)b;
    return strcmp(*ia, *ib);
    /* strcmp functions works exactly as expected from
    comparison function */ 
} 
 
void print_cstring_array(char **array, size_t len) { 
    size_t i;
 
    for (i = 0; i < len; i  ) 
        printf("%s | ", array[i]);
 
    putchar('\n');
} 
 
int main() {
    int v[] = { 5, 4, 3, 2, 1 };
    char *strings[] = { "Zorro", "Alex", "Celine", "Bill", "Forest", "Dexter" };

    int n = sizeof(v) / sizeof(int);
 
    print_int_array(v, n);
 
    generic_quicksort((void *)v, 0, n - 1, sizeof(int), compare_int);
    
    print_int_array(v, n);

    /*

    int s = sizeof(strings) / sizeof(*char);

    print_cstring_array(strings, s);

    generic_quicksort((void *)strings, 0, s - 1, sizeof(*char), compare_str);

    print_cstring_array(strings, s);
*/
    return 0;
} 
 
int compare_int(const void *a, const void *b) {
    return *((int*)a) - *((int*)b);
} 
 
void generic_quicksort(void *v, int i, int f, size_t size, int (*comp)(const void *, const void *)) {
    if (i >= f)
        return;
 
    int p = generic_partition(v, i, f, size, comp);
 
    generic_quicksort(v, i, p - 1, size, comp);
    generic_quicksort(v, p   1, f, size, comp);
}
 
void generic_swap(void *a, void *b, size_t size) {
    void *tmp = malloc(size);
 
    memcpy(tmp, a, size);
    memcpy(a, b, size);
    memcpy(b, tmp, size);
 
    free(tmp);
}
 
int generic_partition(void *v, int i, int f, size_t size, int (*comp)(const void *, const void *)) {
    
    void *x = malloc(size);
    int k, j;

    memcpy(x, v   (i * size), size);

    k = i - 1;

    for (j = i; j <= f - 1; j  ) {
        if (comp(v   (j * size), x) <= 0) {
            k  ;
            generic_swap(v   (k * size), v   (j * size), size);
        }
    }
    
    generic_swap(v   ((k   1) * size), v   (f * size), size);

    free(x);
    
    return (k   1);
}

CodePudding user response:

There are multiple problems in the code:

  • int n = sizeof(v) / sizeof(int); is risky: there is a silent assumption about the type of v. You should write int n = sizeof(v) / sizeof(*v);

  • The convention to pass the indices of the first and last elements of the slice is confusing and not idiomatic in C, you should pass the index of the first element and the index of the element after the last one. This allows for unsigned index types and empty arrays.

  • v (j * size) uses void pointer arithmetics, which is an extension not available on all systems. Use unsigned char pointers for this.

  • the comparison function for integers has undefined behavior for large absolute values because subtracting them may cause an arithmetic overflow. You should use this instead:

    int compare_int(const void *a, const void *b) {
        int ia = *(const int *)a;
        int ib = *(const int *)b;
        return (ia > ib) - (ia < ib);
    }
    
  • generic_swap uses malloc and memcpy. This causes much overhead for small elements, you should use a simple loop:

    void generic_swap(void *a, void *b, size_t size) {
        unsigned char *pa = (unsigned char *)a;
        unsigned char *pb = (unsigned char *)b;
        while (size-- > 0) {
            unsigned char c = *pa;
            *pa   = *pb;
            *pb   = c;
        }
    }
    
  • The generic_partition in the reference uses the last element as the pivot, but you initialize x from the first element. You should write memcpy(x, v (f * size), size);. This is causing the failure. The current code might work by coincidence for the int version. Using the first or the last element as a pivot causes worst case behavior on sorted arrays.

Here is a modified version:

#include <stdio.h>
#include <string.h>

//prototipi delle funzioni
typedef int (*compare_function)(const void *, const void *);

void generic_quicksort(void *v, int i, int f, size_t size, compare_function compare);

//funzione di confronto
int compare_int(const void *a, const void *b) {
    int ia = *(const int *)a;
    int ib = *(const int *)b;
    return (ia > ib) - (ia < ib);
}

int compare_str(const void *a, const void *b) {
    const char *sa = *(const char * const *)a;
    const char *sb = *(const char * const *)b;
    return strcmp(sa, sb);
}

void print_int_array(const int *array, size_t len) {
    size_t i;

    if (len > 0) {
        printf("%d", array[0]);
        for (i = 1; i < len; i  )
            printf("| %d", array[i]);
    }
    putchar('\n');
}

void print_cstring_array(const char * const *array, size_t len) {
    size_t i;

    if (len > 0) {
        printf("%s", array[0]);
        for (i = 1; i < len; i  )
            printf(" | %s", array[i]);
    }
    putchar('\n');
}

static void generic_swap(void *a, void *b, size_t size) {
    unsigned char *pa = (unsigned char *)a;
    unsigned char *pb = (unsigned char *)b;
    while (size-- > 0) {
        unsigned char c = *pa;
        *pa   = *pb;
        *pb   = c;
    }
}

static int generic_partition(void *v, int i, int f, size_t size,
                             int (*comp)(const void *, const void *))
{
    unsigned char *p = (unsigned char *)v;
    int j, k = i;
    // using first element as pivot

    for (j = i   1; j < f; j  ) {
        if (comp(p   j * size, p   i * size) <= 0) {
            k  ;
            generic_swap(p   k * size, p   j * size, size);
        }
    }

    /* swap the pivot to the end of the left part */
    generic_swap(p   i * size, p   k * size, size);
    return k;
}

void generic_quicksort(void *v, int i, int f, size_t size,
                       int (*comp)(const void *, const void *))
{
    if (f > i   1) {
        int p = generic_partition(v, i, f, size, comp);
        generic_quicksort(v, i, p, size, comp);
        generic_quicksort(v, p   1, f, size, comp);
    }
}

int main() {
    int v[] = { 5, 4, 3, 2, 1 };
    int n = sizeof(v) / sizeof(*v);

    const char *strings[] = { "Zorro", "Alex", "Celine", "Bill", "Forest", "Dexter" };
    int s = sizeof(strings) / sizeof(*strings);

    print_int_array(v, n);
    generic_quicksort((void *)v, 0, n, sizeof(*v), compare_int);
    print_int_array(v, n);

    print_cstring_array(strings, s);
    generic_quicksort((void *)strings, 0, s, sizeof(*strings), compare_str);
    print_cstring_array(strings, s);

    return 0;
}

Note that choosing the first or the last element as the pivot leads to worst case complexity for a sorted array. The depth of recursion for generic_quicksort will be the length of the array, potentially causing a stack overflow.

Here is a modified version that is protected against this, but still has quadratic time complexity on a sorted array:

void generic_quicksort(void *v, int i, int f, size_t size,
                       int (*comp)(const void *, const void *))
{
    while (f > i   1) {
        int p = generic_partition(v, i, f, size, comp);
        if (p - i < f - p) {
            generic_quicksort(v, i, p, size, comp);
            i = p   1;
        } else {
            generic_quicksort(v, p   1, f, size, comp);
            f = p;
        }
    }
}
  • Related