为啥我的基数排序 JAVA 实现比快速排序慢?

Posted

技术标签:

【中文标题】为啥我的基数排序 JAVA 实现比快速排序慢?【英文标题】:Why is my Radix Sort JAVA implementation slower than Quick sort?为什么我的基数排序 JAVA 实现比快速排序慢? 【发布时间】:2021-05-11 07:45:35 【问题描述】:

我正在尝试使用 ByteBuffer.allocate() 编写基数代码 我了解到基数排序的时间复杂度为 O(kn),我编写了这段代码以使 k=4。 我还写了快速排序,发现我的基数排序比快速排序慢 2~3 倍。 是因为内存访问效率低吗? 这是我的基数排序代码。

private static int[] radixSort(int[] value)


    byte[][] valueByBytes = new byte[value.length][4];
    for (int i=0; i<value.length; i++) 
        valueByBytes[i] = ByteBuffer.allocate(4).putInt(value[i]).array();
    

    for (int key = 3; key >=0; key--) 
        valueByBytes = countingSort(valueByBytes, key);
    

    for (int i=0; i<value.length; i++) 
        value[i] = ByteBuffer.wrap(valueByBytes[i]).getInt();
    
    return (value);


private static byte[][] countingSort(byte[][] valueByBytes, int key) 
    int[] countingArr = new int[256]; // 0 ~ 255
    byte[][] toReturnArr = new byte[valueByBytes.length][4];
    for (int i=0; i<256; i++) 
        countingArr[i] = 0;
    

    int[] intArr = new int[valueByBytes.length];

    if (key > 0) 
        for (int j=0; j<valueByBytes.length; j++) 
            intArr[j] = (int) valueByBytes[j][key] >= 0 ? valueByBytes[j][key] : 256+valueByBytes[j][key];
        
    
    else 
        for (int j=0; j<valueByBytes.length; j++) 
            intArr[j] = (int) valueByBytes[j][key] + 128;
        
    

    for (int j=0; j<intArr.length; j++) 
        countingArr[intArr[j]]++;
    

    for (int i=1; i<256; i++) 
        countingArr[i] = countingArr[i-1] + countingArr[i];
    

    for (int j=intArr.length-1; j>=0; j--) 
        toReturnArr[countingArr[intArr[j]]-1] = valueByBytes[j];
        countingArr[intArr[j]]--;
    

    return toReturnArr;

【问题讨论】:

首先,您如何确定它慢了 2-3 倍? 首先,正如 kaya3 所说:您是如何验证的?对 Java 代码进行基准测试(尤其是微基准测试)可能非常棘手,而且很容易得到误导性数据。其次,更好的大 O 并不总是转化为更好的实际性能,因为常数和其他低阶因素在现实世界中确实很重要,而在大 O 中被忽略了。第三,基数排序很奇怪,只有当你的键很大时才有意义。 @kaya3 我使用 System.nanoTime() 测量了两个代码的时间,如下所示: long t = System.nanoTime(); v = xxSort(v); System.out.println((System.nanoTime() - t)/1000)); 什么是v @kaya3 是一个整数数组 【参考方案1】:

基数排序需要 O(xkn) 时间。其中 K 是位数。 快速排序需要 O(ynlog n) 其中 x > y,从较长的密钥中提取位可能是一项昂贵的操作。 此处使用的开销可能会给您的情况带来麻烦。

您可以参考这个问题When should we use Radix sort?获得更多答案

【讨论】:

假设您的意思是 x 和 y 是常数,那么这是对大 O 表示法的误用。【参考方案2】:

将 ByteBuffer 与所有复制|转换操作一起使用会减慢基数排序。我测试了对 16777216 个整数进行排序,在我的系统上,(Intel 3770K,Windows 7 Pro 64 位,Netbeans 8.2),问题的基数排序大约需要 16.7 秒,而下面显示的基数排序的实现大约需要 0.55 秒(大约 30 次更快),而下面显示的快速排序的实现大约需要 1.8 秒。

package x;
import java.util.Random;

public class x 

    public static void RadixSort(int[] a)
    
        int count = a.length;
        if(count < 2)
            return;
        int[] b = new int[count];           // allocate working array
        int [][] mIndex = new int[4][256];  // histograms | indexes
        int i,j,m,n,u;
        for(i = 0; i < count; i++)         // generate histograms
            u = a[i];
            mIndex[0][u&0xff]++;
            u >>= 8;
            mIndex[1][u&0xff]++;
            u >>= 8;
            mIndex[2][u&0xff]++;
            u >>= 8;
            mIndex[3][u+128]++;
        
        for(j = 0; j < 4; j++)             // convert to indices
            m = 0;
            for(i = 0; i < 256; i++)
                n = mIndex[j][i];
                mIndex[j][i] = m;
                m += n;
                   
        
        for(i = 0; i < count; i++)         //  radix sort
            u = a[i];
            m = u&0xff;
            b[mIndex[0][m]++] = u;
        
        for(i = 0; i < count; i++)
            u = b[i];
            m = (u>>8)&0xff;
            a[mIndex[1][m]++] = u;
        
        for(i = 0; i < count; i++)
            u = a[i];
            m = (u>>16)&0xff;
            b[mIndex[2][m]++] = u;
        
        for(i = 0; i < count; i++)
            u = b[i];
            m = (u>>24)+128;
            a[mIndex[3][m]++] = u;
        
    

    public static void main(String[] args) 
        int[] a = new int[16*1024*1024];
        Random r = new Random(0);
        for(int i = 0; i < a.length; i++)
            a[i] = r.nextInt();
        long bgn, end;
        bgn = System.currentTimeMillis();
        RadixSort(a);
        end = System.currentTimeMillis();
        for(int i = 1; i < a.length; i++)
            if(a[i-1] > a[i])
                System.out.println("failed");
                break;
            
        
        System.out.println("milliseconds " + (end-bgn));
    


    @SuppressWarnings("empty-statement")
    public static void qsort(int[] a, int lo, int hi)
    
        if(lo >= hi)
            return;
        int  p = a[lo+(hi-lo)/2];
        int  i = lo-1;
        int  j = hi+1;
        int t;
        while(true)                // partition
            while(a[++i] < p);
            while(a[--j] > p);
            if(i >= j)
                break;
            t     = a[i];
            a[i] = a[j];
            a[j] = t;
        
        qsort(a, lo, j);
        qsort(a, j+1, hi);
    

【讨论】:

感谢分享!

以上是关于为啥我的基数排序 JAVA 实现比快速排序慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的分拣程序这么慢? (java中的基数/桶排序)

为啥在我的情况下快速排序总是比冒泡排序慢?

八大排序算法Java实现(下)-快排归排基数排序

快速排序及优化

排序算法——快速排序

快速排序