这个Radix Sort代码中的最后一个for循环是做什么的?

时间:2019-06-03 18:16:47

标签: c radix-sort

我正在阅读Zed A. Shaw的《学习硬方法》( Learn C The Hard Way )一书,当时正在研究他对基数排序算法的实现。

这是他的代码:

#define ByteOf(x, y) (((u_int8_t *)x)[y])

static inline void radix_sort(short offset, uint64_t max,
        uint64_t * source, uint64_t * dest)
{
  uint64_t count[256] = { 0 };
  uint64_t *cp = NULL;
  uint64_t *sp = NULL;
  uint64_t *end = NULL;
  uint64_t s = 0;
  uint64_t c = 0;

  // Count occurences of every byte value
  for (sp = source, end = source + max; sp < end; sp++) {
    count[ByteOf(sp, offset)]++;
  }

  // transform count into index by summing
  // elements and storing them into same array.
  for (s = 0, cp = count, end = count + 256; cp < end; cp++) {
    c = *cp;
    *cp = s;
    s += c;
  }

  // fill dest with right values in the right place
  for (sp = source, end = source + max; sp < end; sp++) {
    cp = count + ByteOf(sp, offset);
    printf("dest[%d] = %d\n", *cp, *sp);
    dest[*cp] = *sp;
    ++(*cp);
  }
}

以上仅是一个辅助功能。他的实际基数排序在这里完成:

void RadixMap_sort(RadixMap * map)
{
  uint64_t *source = &map->contents[0].raw;
  uint64_t *temp = &map->temp[0].raw;

  radix_sort(0, map->end, source, temp);
  radix_sort(1, map->end, temp, source);
  radix_sort(2, map->end, source, temp);
  radix_sort(3, map->end, temp, source);
}

这是他定义的结构:

typedef union RMElement {
  uint64_t raw;
  struct {
    uint32_t key;
    uint32_t value;
  } data;
} RMElement;

typedef struct RadixMap {
  size_t max;
  size_t end;
  uint32_t counter;
  RMElement *contents;
  RMElement *temp;
} RadixMap;

我可以理解内联函数radix_sort中的前两个for循环。据我了解,第一个函数只是对字节值进行计数,第二个函数基本上是一个累积频率表,其中每个条目都是先前条目的总和。

我仍然无法绕过ByteOf(x, y)宏和第三个for循环。我尝试阅读Wikipedia page的Radix-sort,并且阅读了使用C ++实现的another article。但是,每一篇文章中编写的代码与他编写的代码都不匹配。

我了解Radix Sort的原理。基本上,我们根据每个数字将其分组,为遇到的每个新数字重新排列分组。例如,要对数组[223, 912, 275, 100, 633, 120, 380]进行排序,请首先将它们按一位进行分组,以便得到[380, 100, 120][912][633, 223][275]。然后,对数万个位置执行相同的操作,直到数字用完。

任何帮助解释他的代码的人将不胜感激。 谢谢。

1 个答案:

答案 0 :(得分:1)

ByteOf(x,y)与:

#define ByteOf(x, y)  ((*(x) >> (offset*8)) & 0xff)

也就是说,它将字节#{offset}的值隔离在一个值内。

第二个循环是一种分配器。如果在第一个循环之后前六个count []是1,2,4,0,16,25,则在第二个循环之后它们将是0,1,3,7,7,23。这指示第三个循环(在source []上)将目标布局为:

ByteOf       index       number of values
0            0           1
1            1           2
2            3           4
3            7           0 -- there are none.
4            7           16
5            23          25

我发现将第三个循环重写为:

  for (i = 0; i < max; i++) {
    dest[count[ByteOf((source+i), offset)]++] = source[i];
  }

我认为它更清楚地显示了这种关系,即将ith的源元素复制到dest中的索引中。 dest中的索引位于先前为此 digit 计算的分区(count [])的开头。由于此位置现在有一个数字,因此我们增加该分区的开始位置以防止覆盖它。

请注意,必须使用(source + i)括起来的方括号才能为ByteOf中的转换找到正确的地址。