基数排序基数 16(十六进制)

Posted

技术标签:

【中文标题】基数排序基数 16(十六进制)【英文标题】:Radix Sort Base 16 (Hexadecimals) 【发布时间】:2017-03-14 00:45:51 【问题描述】:

我花了 10 小时以上的时间尝试在 LSD 基数排序中对以下(十六进制)进行排序,但无济于事。网上关于这个主题的资料很少。

0 4c7f cd80 41fc 782c 8b74 7eb1 9a03 aa01 73f1

我知道我必须屏蔽并执行按位运算来处理每个十六进制数字(4 位),但不知道如何以及在哪里。

我正在使用来自GeeksforGeeks的代码(我理解)

void rsort(int a[], int n) 
    int max = getMax(a, n);
    for (int exp = 1; max / exp > 0; exp *= 10)    
        ccsort(a, n, exp);
    


int getMax(int a[], int n) 
    int max = a[0];
    int i = 0;
    for (i = 0; i < n; i++) 
        if (a[i] > max) 
            max = a[i];
        
    
    return max;


void ccsort(int a[], int n, int exp) 

    int count[n];
    int output[n];
    int i = 0;

    for (i = 0; i < n; i++) 
        count[i] = 0;
        output[i] = 0;
    
    for (i = 0; i < n; i++) 
        ++count[(a[i] / exp) % 10];
    
    for (i = 1; i <= n; i++) 
        count[i] += count[i - 1];
    
    for (i = n - 1; i >= 0; i--) 
        output[count[(a[i] / exp) % 10] - 1] = a[i];
        --count[(a[i] / exp) % 10];
    
    for (i = 0; i < n; i++) 
        a[i] = output[i];
    

我也检查了所有关于这个问题的 ***,但没有一个包括细节。

【问题讨论】:

变量exp 未正确使用。见this article for an example。您需要向下滚动到标题为“C 中的示例” 的部分。请注意,它们的 exp 从 1 开始,并在每次通过循环时乘以基数。 @WeatherVane,不是文本,它们是数组的一部分,比如主函数中的数组。 【参考方案1】:

您的基数排序实现有点不正确:

它不能处理负数 函数ccsort() 中的数组count[] 的大小应为10 而不是n。如果n 小于10,则该函数不起作用。 用于累积计数的循环走得太远了:for (i = 1; i &lt;= n; i++)&lt;= 运算符再次导致错误。 您说您按十六进制数字排序,但代码使用十进制数字。

这是一个(略微)改进的版本,带有解释:

void ccsort(int a[], int n, int exp) 

    int count[10] =  0 ;
    int output[n];
    int i, last;

    for (i = 0; i < n; i++) 
        // compute the number of entries with any given digit at level exp
        ++count[(a[i] / exp) % 10];
    
    for (i = last = 0; i < 10; i++) 
        // update the counts to have the index of the place to dispatch the next
        // number with a given digit at level exp
        last += count[i];
        count[i] = last - count[i];
    
    for (i = 0; i < n; i++) 
        // dispatch entries at the right index for its digit at level exp
        output[count[(a[i] / exp) % 10]++] = a[i];
    
    for (i = 0; i < n; i++) 
        // copy entries batch to original array
        a[i] = output[i];
    


int getMax(int a[], int n) 
    // find the largest number in the array
    int max = a[0];
    for (int i = 1; i < n; i++) 
        if (a[i] > max) 
            max = a[i];
        
    
    return max;


void rsort(int a[], int n) 
    int max = getMax(a, n);
    // for all digits required to express the maximum value
    for (int exp = 1; max / exp > 0; exp *= 10)    
        // sort the array on one digit at a time
        ccsort(a, n, exp);
    

由于所有的除法和模运算,上述版本效率很低。可以使用移位和掩码来执行十六进制数字:

void ccsort16(int a[], int n, int shift) 

    int count[16] =  0 ;
    int output[n];
    int i, last;

    for (i = 0; i < n; i++) 
        ++count[(a[i] >> shift) & 15];
    
    for (i = last = 0; i < 16; i++) 
        last += count[i];
        count[i] = last - count[i];
    
    for (i = 0; i < n; i++) 
        output[count[(a[i] >> shift) & 15]++] = a[i];
    
    for (i = 0; i < n; i++) 
        a[i] = output[i];
    


void rsort16(int a[], int n) 
    int max = a[0];
    for (int i = 1; i < n; i++) 
        if (a[i] > max) 
            max = a[i];
        
    
    for (int shift = 0; (max >> shift) > 0; shift += 4)    
        ccsort16(a, n, shift);
    

使用包含 256 个条目的 count 数组一次排序一个字节的速度大约是两倍。如 rcgldr 的回答所示,一次计算所有数字的计数也会更快。

请注意,此实现仍然无法处理负数。

【讨论】:

我明白你的观点。我认为在使用循环、标志和交换等方式对列表进行排序后,负数很容易排序。 wb 无符号浮点数? @itproxti:如果您希望对有符号整数进行排序,您可以通过添加INT_MIN 的偏移量来修改上述代码,并且基数排序将正确处理负数。使用当前代码,负数按递增值排序,但在数组末尾分组。可以通过循环将它们移到开头,但在原地执行此操作很棘手。关于浮点值,基数排序算法有时可以使用,但不能移植,因为浮点的内部表示因一个系统而异,可能不适合基数排序。【参考方案2】:

有一种更简单的方法来实现基数排序。检查最大值后,找到 16 >= 最大值的最低幂。这可以通过循环中的 max >>= 4 来完成,增加 x 以便当 max 变为零时,x 的 16 次幂 >= 原始最大值。例如,最大值 0xffff 需要 4 次基数排序,而最大值 0xffffffff 需要 8 次基数排序。

如果值的范围最有可能采用整数可用的完整范围,则无需费心确定最大值,只需根据整数大小进行基数排序。

您的示例代码显示了一个基数排序,由于计数转换为索引的方式,它向后扫描数组。这可以通过使用另一种方法将计数转换为索引来避免。以下是 32 位无符号整数的基数 256 基数排序示例。它使用计数/索引矩阵,因此所有 4 行计数都是通过数组的一次读取传递生成的,然后是 4 次基数排序传递(因此排序后的数据最终回到原始数组中)。 std::swap 是一个交换指针的 C++ 函数,对于 C 程序,这可以通过内联交换指针来替换。 t = 一个; a = b; b = t,其中 t 的类型为 uint32_t *(ptr 为无符号 32 位整数)。对于基数为 16 的基数排序,矩阵大小为 [8][16]。

//  a is input array, b is working array
uint32_t * RadixSort(uint32_t * a, uint32_t *b, size_t count)

size_t mIndex[4][256] = 0;            // count / index matrix
size_t i,j,m,n;
uint32_t u;
    for(i = 0; i < count; i++)         // generate histograms
        u = a[i];
        for(j = 0; j < 4; j++)
            mIndex[j][(size_t)(u & 0xff)]++;
            u >>= 8;
               
    
    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(j = 0; j < 4; j++)             // radix sort
        for(i = 0; i < count; i++)     //  sort by current lsb
            u = a[i];
            m = (size_t)(u>>(j<<3))&0xff;
            b[mIndex[j][m]++] = u;
        
        std::swap(a, b);                //  swap ptrs
    
    return(a);

【讨论】:

所以,如果我理解正确的话。你是说不是一次排序整数(4个字节),而是一次排序1个字节,作为回报会加速算法?我仍然看不到它。谢谢! @itproxti - 基数排序是对整数进行排序,一次 4 个字节,但基数排序的每次传递都基于每个整数中的一个字节字段。第一遍根据最低有效字节对整数进行排序,第四遍和最后一遍根据最高有效字节对整数进行排序。因此,总共是一次读取传递来创建计数/索引矩阵,然后是四次基数排序传递来对数据进行排序。相比之下,原始问题示例需要传递 9 或 10 次才能对 32 位整数进行排序,因为它每次通过一位小数位进行排序。【参考方案3】:
void int_radix_sort(void) 
    int group; //because extracting 8 bits
    int buckets = 1 << 8; //using size 256
    int map[buckets];   
    int mask = buckets - 1;
    int i;
    int cnt[buckets];
    int flag = NULL;
    int partition;
    int *src, *dst;

    for (group = 0; group < 32; group += 8) 
        // group = 8, number of bits we want per round, we want 4 rounds
        // cnt  
        for (int i = 0; i < buckets; i++) 
            cnt[i] = 0;
        
        for (int j = 0; j < n; j++) 
            i = (lst[j] >> group) & mask;
            cnt[i]++; 
            tmp[j] = lst[j];
        

        //map
        map[0] = 0;
        for (int i = 1; i < buckets; i++) 
            map[i] = map[i - 1] + cnt[i - 1];
        

        //move
        for (int j = 0; j < n; j++)    
            i = (tmp[j] >> group) & mask;
            lst[map[i]] = tmp[j];
            map[i]++;
        
    

经过数小时的研究,我找到了答案。我仍然不明白此代码/答案中发生了什么。我无法理解这个概念。希望有人能解释一下。

【讨论】:

【参考方案4】:

我明白你的观点。我认为在使用循环、标志和交换等方式对列表进行排序后,负数很容易排序。 wb 无符号浮点数? – itproxti 2016 年 11 月 1 日 16:02

至于处理浮点数可能有办法,例如345.768是数字,需要转换成整数,即345768,我乘以1000。就像偏移量将 -ve 数字移动到 +ve 域一样,乘以 1000、10000 等会将浮点数转换为小数部分全为零的数字。然后可以将它们类型转换为 int 或 long。但是对于较大的值,整个重整后的数字可能无法容纳在整个 int 或 long 范围内。

要相乘的数字必须是常数,就像偏移量一样,这样才能保留大小之间的关系。最好使用 2 的幂,例如 8 或 16,因为可以使用位移运算符。然而,就像计算偏移量需要一些时间一样,计算乘数也需要一些时间。将搜索整个数组以计算最小的数字,当乘以时将所有数字在小数部分为零。

这可能计算速度不快,但如果需要,仍然可以完成这项工作。

【讨论】:

以上是关于基数排序基数 16(十六进制)的主要内容,如果未能解决你的问题,请参考以下文章

从十进制基数转换为十六进制基数c ++

C中二进制表示的基数排序算法

第十六周 项目1--验证算法--基数排序

第十六周 项目1--验证算法--基数排序

数据结构Java版之基数排序

第十六周 项目4--英语单词的基数排序