C 语言实现标准库中的 `qsort`

April 2, 2018

我发现我果然不适合用脑子去想问题,写这东西实在是太烧脑了。为了能够写出这份代码,我浪费了整整一个下午打游戏的时光和半个晚上,想想真是可惜,我美好的周末啊。

ver 1.0

这份代码实现了一个排序,就是标准库中的牌序,那个支援各种数据的牌序。我这里也实现了。至少测试通过了基本类型。

入口引数和标准库的qsort完全相同,分别是数组地址,数组长度,数组中每个元素占据的字节数和比较函式。

#include <stdio.h>

/*
Parameter:
    arr : address of array
    len : sizeof(array/array[0])
    size: sizeof(array[0])
    func: this func return 1 (first arg first) -1 (second arg first)
*/

void myqsort(void *arr,int len,int size,int (*func)(const void *,const void *)){
    void *i, *j;
    int k;
    i = (void *)((int)arr + size);
    j = (void *)((int)arr + (len-1)*size);

    if(len == 1 || len == 0)return;

    while(i <= j){

        /* 寻找要交换的元素 */
        while( func(arr,i) < 0 && i < j){
            i = (void*)((int)i + size);
        }
        while( func(arr,j) > 0 && j >= i){
            j = (void*)((int)j - size);
        }

        /* 交换之 */
        if( j > i ){
            for(k=0; k<size/sizeof(char); k++){
                char x = *((char *)(i)+k);
                *((char *)i+k) = *((char *)j+k);
                *((char *)j+k) = x;
            }
        }

        i = (void*)((int)i + size);
    }

    /* 交换 arr[0] 和 j */
    if( arr != j ){
        for(k=0; k<size/sizeof(char); k++){
            char x = *((char *)(j)+k);
            *((char *)j+k) = *((char *)arr+k);
            *((char *)arr+k) = x;
        }
    }

    /* 继续快排 */
    myqsort(arr,((int)j-(int)arr)/size,size,func);
    myqsort((void*)((int)j+size),((int)arr + (len-1)*size-(int)j)/size,size,func);
}

#define T   double

T a[] = {1.5,1.4,3.2,4.2,5.4,9.1,8.1,8.3,8.2};

int cmp(const void *a,const void *b){
    return *(T *)a > *(T *)b ? 1 : -1;
}

int main(){
    int i = 0;
    for(i=0; i<sizeof(a)/sizeof(a[0]); i++){
        printf("%g  ",(double)a[i]);
    }
    printf("\n");

    myqsort(a,sizeof(a)/sizeof(a[0]),sizeof(a[0]),cmp);

    for(i=0; i<sizeof(a)/sizeof(a[0]); i++){
        printf("%g  ",(double)a[i]);
    }
    printf("\n");

    return 0;
}

我写得肯定还有能够改进的部分,也许还有我不清楚的bug。还请诸君见笑。

ver1.1

改写了交换的写法。参考了一些其他人写法,将最后一个函式呼叫改为使用goto。

#include <stdio.h>
#include <inttypes.h>

/*
Swap a and b:
    global  : _w(uint8_t)
*/
static uint8_t _w;
#define swap_(a,b,_width)   for(_w=0; _w<_width; _w++){                     \
                                char __x = *((char*)(a)+_w);                \
                                *((char *)a+_w) = *((char *)b+_w);          \
                                *((char *)b+_w) = __x;                      \
                            }
#define swap(a,b)       swap_(&(a),&(b),sizeof(a)/sizeof(char))

/*
Parameter:
    arr : address of array
    len : sizeof(array/array[0])
    size: sizeof(array[0])
    func: this func return 1 (first arg first) -1 (second arg first)
*/
void myqsort(void *arr,int len,int size,int (*func)(const void *,const void *)){
    void *i, *j;
    int len2;
loop:
    i = (void *)((int)arr + size);
    j = (void *)((int)arr + (len-1)*size);

    while(i <= j){

        /* 寻找要交换的元素 */
        while( func(arr,i) < 0 && i < j){
            i = (void*)((int)i + size);
        }
        while( func(arr,j) > 0 && j >= i){
            j = (void*)((int)j - size);
        }

        /* 交换之 */
        if( j > i ){
            swap_(i,j,size/sizeof(char));
        }

        i = (void*)((int)i + size);
    }

    /* 交换 arr[0] 和 j */
    if( arr != j ){
        swap_(arr,j,size/sizeof(char));
    }

    /* 继续快排 */
    len2 = ((int)j-(int)arr)/size;
    if(len2 > 1){
        myqsort(arr,len2,size,func);
    }

    len2 = ((int)arr + (len-1)*size-(int)j)/size;
    if(len2 > 1){
        /* myqsort((void*)((int)j+size),len2,size,func); */
        /* 使用 goto 节省一次函式呼叫 */
        len = len2;
        arr = (void*)((int)j+size);
        goto loop;
    }
}

#define T   int

T a[] = {1.5,1.4,3.2,4.2,0.0,5.4,9.1,8.1,8.3,8.2};

int cmp(const void *a,const void *b){
    return *(T *)a < *(T *)b ? 1 : -1;
}

debug(){
    double a = 10.4, b = 2.8;
    struct{
        int x,y;
    }p1 = {1,2}, p2 = {3,4};

    printf("%g %g\n",(double)a,(double)b);
    swap(a,b);
    printf("%g %g\n",(double)a,(double)b);

    printf("(%g,%g) (%g,%g)\n",(double)p1.x,(double)p1.y,(double)p2.x,(double)p2.y);
    swap(p1,p2);
    printf("(%g,%g) (%g,%g)\n",(double)p1.x,(double)p1.y,(double)p2.x,(double)p2.y);
}

int main(){
    int i = 0;

    debug();

    for(i=0; i<sizeof(a)/sizeof(a[0]); i++){
        printf("%g  ",(double)a[i]);
    }
    printf("\n");

    myqsort(a,sizeof(a)/sizeof(a[0]),sizeof(a[0]),cmp);

    for(i=0; i<sizeof(a)/sizeof(a[0]); i++){
        printf("%g  ",(double)a[i]);
    }
    printf("\n");

    return 0;
}

加入的debug函式是用来测试swap的。关于swap函式可以参考我写的另一篇文章。

备考