欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基数排序的一个变形应用

程序员文章站 2022-05-24 15:07:13
...

  说起排序,大多数人在实际项目中很少自己去写一个排序,一般来说,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万数据进行测试,在我电脑上的运行结果是:

基数排序的一个变形应用
            
    
    博客分类: 算法数据结构计算机 基数排序的一个变形应用排序基数排序 

你会发现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;
}

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

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

最后的排序测试结果:

基数排序的一个变形应用
            
    
    博客分类: 算法数据结构计算机 基数排序的一个变形应用排序基数排序 

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

 

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

 

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