基数排序的一个变形应用

  说起排序,大多数人在实际项目中很少自己去写一个排序,一般来说,qsort一行话就可以了。我也很少在实际项目中用到过基数排序,最近,写了一篇博客文章叫做: 字符串之全文索引 ,这篇文章的下一篇文章 要用到一个倍增算法。这个倍增算法,就可以非常巧妙的运用基数排序。作为那篇文章的一个铺垫,我专门写了一篇基数排序的文章。这篇文章里面的基数排序肯定是一个变形。

大多数网上 或者 书上的基数排序都是从下面的例子开始的:

排序下面的数列:

73  22  93  43  55  14  28  65  39  81

然后对这些数字,用个位数进行排序:

0                  
1 81                
2 22                
3 73 93 43            
4 14                
5 55 65              
6                  
7                  
8 28                
9 39                

从这个二维的数组里面顺序取出:

81 22 73 93 43 14 55 65 28 39

 

再对10位数进行排序:

0                  
1 14                
2 22 28              
3 39                
4 43                
5 55 65              
6                  
7 73                
8 81                
9 93                

 

从这个二维数组里面顺序取出:

14 22 28 39 43 55 65 73 81 93

上面的思路写成代码也非常的容易写:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int data[10]={73,22,93,43,55,14,28,65,39,81};
    int temp[10][10]= {0};
    int order[10]={0};
    int i,j,k,n,lsd;
 
    printf("\n排序前: ");
    for (i=0; i<10; i++) printf("%d ",data[i]);
    putchar('\n');
    n=1;
    while (n<=10)
    {
        for (i=0;i<10;i++) 
        {
            lsd=((data[i]/n)%10);
            temp[lsd][order[lsd]]=data[i];
            order[lsd]++;
        }
        printf("\n重新排列: ");
        k = 0;
        for (i=0;i<10;i++)
        {
            if(order[i]!=0)
            {
                for (j=0;j<order[i];j++)
                {
                    data[k]=temp[i][j];
                    printf("%d ",data[k]);
                    k++;
                }
                order[i]=0;
            }
        }
        n *= 10;
    }
    putchar('\n');
    printf("\n排序后: ");
    for (i=0; i<10; i++) printf("%d ",data[i]);
    return 0;
}

既然这个基数排序理论上性能比较的高,这样的话,我们就写个程序比较一下实际上快速排序和基数排序的速度。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <algorithm>
 
#define MAX_N 5000000
int sort[10][MAX_N];
int data[MAX_N];
int data2[MAX_N];
int data3[MAX_N];
int radix_sort(int data[], int max_index, int max_number);
static int range_rand(int min, int max);
 
int intcmp(const void *a, const void *b)
{
    return *((int *)a) - *((int *)b);
}
 
int main()
{
    int i;
    int max = -1;
    clock_t t;
    for (i = 0; i < MAX_N; i++)
    {
        data[i] = range_rand(1, 0xFFFFFFF);
        if (max < data[i]) max = data[i];
    }
    memcpy(data2, data, sizeof(data));
    memcpy(data3, data, sizeof(data));
    t = clock();
    qsort(data, MAX_N, sizeof(int), intcmp);
    printf("qsort cost : %d \n", clock() - t);
 
    t = clock();
    radix_sort(data2, MAX_N, max);
    printf("radix cost : %d \n", clock() - t);
 
    t = clock();
    std::sort(data3, data3 + MAX_N);
    printf("std::sort cost : %d \n", clock() - t);
 
    for (i = 0; i < 20; i++)
    {
        printf("%d ", data[i]);
    }
    printf("\n");
    for (i = 0; i < 20; i++)
    {
        printf("%d ", data2[i]);
    }
    printf("\n");
 
    for (i = 0; i < 20; i++)
    {
        printf("%d ", data3[i]);
    }
    printf("\n");
}
 
int radix_sort(int data[], int max_index, int max_number)
{
    
    int number[10];
    int lsd, i, k, j, n;
    n = 1;
    memset(number, 0, sizeof(number));
    while (n <= max_number)
    {
        for (i = 0; i < max_index; i++)
        {
            lsd = (data[i]/n) % 10;
            sort[lsd][number[lsd]] = data[i];
            number[lsd]++;
        }
        k = 0;
        for (i = 0; i < 10; i++)
        {
            if(number[i] != 0)
            {
                for (j = 0; j < number[i];j++)
                {
                    data[k] = sort[i][j];
                    k++;
                }
                number[i] = 0;
            }
        }
        n *= 10;
    }
    return 0;
}
 
static int range_rand(int min, int max)
{
    double r = 0;
    int    i;
    double mul = 1;
    for (i = 0; i < 3; i++)
    {
        mul *= 0.0001;
        r += (rand() % 10000) * mul;
    }
    //0 - 1 中的一个随机数
    return (int)(r * (max - min)) + min;
}

我用了500万数据进行测试,在我电脑上的运行结果是:

image

你会发现C++ stl 里面的sort 是最快的(没有函数调用的损失)。radix sort  和 qsort 性能差不多。所以,在某个数字可能很大的时候,上面的这个算法没有任何性能上的优势,还会浪费非常多的内存。

 

基数排序大多数情况下面只适用于下面的情景,一组数,这组数的最大值不是很大,更加准确的说,是要排序的对象的数目 和 排序对象的最大值之间相差不多。比如,这组数 1 4 5 2 2,要排序对象的数目是 5 ,排序对象的最大值也是 5. 这样的情况很适合。我们原来的排序的数,默认是以10为基数,现在,这个改进算法是这样的,我以最大的那个数为基数,这样,所有的数都是“个位数”,问题就简单了。

说了这样多,我觉得还是用程序来表达比较的清晰:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <algorithm>
 
#define MAX_N 5000000
 
int sort[10][MAX_N];
int quick_radix_sort[MAX_N];
int data[MAX_N];
int data2[MAX_N];
int data3[MAX_N];
int data4[MAX_N];
 
int qradix_sort(int data[], int max_index);
int radix_sort(int data[], int max_index, int max_number);
static int range_rand(int min, int max);
 
int intcmp(const void *a, const void *b)
{
    return *((int *)a) - *((int *)b);
}
 
int main()
{
    int i;
    int max = -1;
    clock_t t;
    //初始化随机数种子
    srand ( time(NULL) );
    for (i = 0; i < MAX_N; i++)
    {
        data[i] = range_rand(1, MAX_N - 1);
        if (max < data[i]) max = data[i];
    }
    memcpy(data2, data, sizeof(data));
    memcpy(data3, data, sizeof(data));
    memcpy(data4, data, sizeof(data));
 
    t = clock();
    qsort(data, MAX_N, sizeof(int), intcmp);
    printf("qsort cost : %d \n", clock() - t);
 
    t = clock();
    radix_sort(data2, MAX_N, max);
    printf("radix cost : %d \n", clock() - t);
 
    t = clock();
    std::sort(data3, data3 + MAX_N);
    printf("std::sort cost : %d \n", clock() - t);
 
    t = clock();
    qradix_sort(data4, MAX_N);
    printf("quick radix sort cost : %d \n", clock() - t);
 
    for (i = 0; i < 20; i++)
    {
        printf("%d ", data[i]);
    }
    printf("\n");
    for (i = 0; i < 20; i++)
    {
        printf("%d ", data2[i]);
    }
    printf("\n");
 
    for (i = 0; i < 20; i++)
    {
        printf("%d ", data3[i]);
    }
    printf("\n");
 
    for (i = 0; i < 20; i++)
    {
        printf("%d ", data4[i]);
    }
    printf("\n");
 
}
 
int qradix_sort(int data[], int max_index)
{
    int i, j, n = 0;
    for (i = 0; i < max_index; i++)
    {
        quick_radix_sort[data[i]]++;
    }
    for (i = 0; i < max_index; i++)
    {
        for (j = 0; j < quick_radix_sort[i]; j++)
        {
            data[n++] = i;
        }
    }
    return 0;
}
 
int radix_sort(int data[], int max_index, int max_number)
{
    
    int number[10];
    int lsd, i, k, j, n;
    n = 1;
    memset(number, 0, sizeof(number));
    while (n <= max_number)
    {
        for (i = 0; i < max_index; i++)
        {
            lsd = (data[i]/n) % 10;
            sort[lsd][number[lsd]] = data[i];
            number[lsd]++;
        }
        k = 0;
        for (i = 0; i < 10; i++)
        {
            if(number[i] != 0)
            {
                for (j = 0; j < number[i];j++)
                {
                    data[k] = sort[i][j];
                    k++;
                }
                number[i] = 0;
            }
        }
        n *= 10;
    }
    return 0;
}
 
static int range_rand(int min, int max)
{
    double r = 0;
    int    i;
    double mul = 1;
    for (i = 0; i < 3; i++)
    {
        mul *= 0.0001;
        r += (rand() % 10000) * mul;
    }
    //0 - 1 中的一个随机数
    return (int)(r * (max - min)) + min;
}

 

这个代码其实就是在上面的测试代码的基础上加上了一个qradix_sort 函数,这个函数非常的简单:

int qradix_sort(int data[], int max_index)
{
    int i, j, n = 0;
    for (i = 0; i < max_index; i++)
    {
        quick_radix_sort[data[i]]++;
    }
    for (i = 0; i < max_index; i++)
    {
        for (j = 0; j < quick_radix_sort[i]; j++)
        {
            data[n++] = i;
        }
    }
    return 0;
}

循环体里面就两句话,和我们前面的那个基数排序不是很一样,这里,已经不用一个二维数组保存排序的数字了,只是标记一下这个数字有几个,因为,下标其实就是被排序的数字。

重新排序这个循环也很简单,仔细冥想一下怎么回事,不懂就在草稿纸上画画草图吧。这里主要的思想还是,下标就是排序的数字。

最后的排序测试结果:

image

我们发现,现在普通的radix性能也提高了,因为,现在数字变小了,循环的次数也变少了。快速基数排序的性能提高还是非常的明显。普通基数排序的两倍,std::sort 的3倍,qsort的 5倍,最重要的是代码非常的简单,基本上是你见过的最简单的一个排序了。

 

对一个真正的程序员来说,很少这样要死抠一个程序的性能,一般情况下,也是得不偿失。只有,在某个东西真正成了一个性能瓶颈的时候,我们才需要去关心一下:是不是可以这样改进一下。

 

这个排序算法,将来会应用到 字符串处理的倍增算法里面,这个倍增算法,要反复的进行排序。如果,排序能快一点,这个程序就能快很多。

你可能感兴趣的:(排序,基数排序,基数排序的一个变形应用)