• 基数排序的一个变形应用


        说起排序,大多数人在实际项目中很少自己去写一个排序,一般来说,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倍,最重要的是代码非常的简单,基本上是你见过的最简单的一个排序了。

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

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

  • 相关阅读:
    5.装饰器生成器迭代器
    python-进程&线程
    模块(二)
    MYSQL数据库操作(一)
    python-网络编程-socket编程
    python-异常处理
    python-面向对象进阶
    python-模块
    python_面向对象编程
    生成器 迭代器,装饰器 ,软件开发规范
  • 原文地址:https://www.cnblogs.com/niniwzw/p/quick_radix_sort.html
Copyright © 2020-2023  润新知