【问题标题】:Radix Sort Base 16 (Hexadecimals)基数排序基数 16(十六进制)
【发布时间】: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];
    }
}

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

【问题讨论】:

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

标签: c sorting radix-sort


【解决方案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,因为可以使用位移运算符。然而,就像计算偏移量需要一些时间一样,计算乘数也需要一些时间。将搜索整个数组以计算最小的数字,当乘以时将所有数字在小数部分为零。

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

    【讨论】:

      猜你喜欢
      • 2023-03-08
      • 2021-04-06
      • 1970-01-01
      • 2011-07-15
      • 2023-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多