这个基数排序代码中的最后一个“for”循环是做啥的?

Posted

技术标签:

【中文标题】这个基数排序代码中的最后一个“for”循环是做啥的?【英文标题】:What does the last `for` loop in this Radix Sort code do?这个基数排序代码中的最后一个“for”循环是做什么的? 【发布时间】:2019-06-03 18:16:47 【问题描述】:

我正在阅读 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 的基数排序,我阅读了使用 C++ 实现的 another article。但是,这些文章中写的代码与他写的代码不匹配。

我了解基数排序原则上的工作原理。基本上,我们根据每个数字对其进行分组,为我们遇到的每个新数字重新安排分组。例如,要对数组[223, 912, 275, 100, 633, 120, 380] 进行排序,首先将它们按个位数字分组,得到[380, 100, 120][912][633, 223][275]。然后你对十位和一百位做同样的事情,直到你用完数字。

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

【问题讨论】:

我认为这是一个以 256 为基数的基数排序,而不是您习惯看到的以 10 为基数的排序(来自您的链接文章)。这样做的好处是您可以通过查看数字的字节(这是 ByteOf 的用途)轻松地将值分组到存储桶中,而不是使用除法和模数。 那么让我看看我是否理解ByteOf。基本上,ByteOf 采用 uint64_t 指针并将其转换为字节数组 (uint8_t)。然后,访问索引y 处的字节。这是ByteOf 在做什么吗? 【参考方案1】:

ByteOf(x, y) 等同于:

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

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

第二个循环是一种分配器。如果前六个计数 [] 在第一个循环之后是 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];
  

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

请注意,(source+i) 周围的括号对于在 ByteOf 中获得正确的转换地址是必要的。

【讨论】:

我仍然不确定我是否理解第三个 for 循环。基本上,我不明白我们是如何计算dest 的索引的。 另外,如果我理解正确,那么ByteOf 基本上只是将x 作为指向某些字节的指针访问,然后以y 为索引对这个字节“数组”进行索引?

以上是关于这个基数排序代码中的最后一个“for”循环是做啥的?的主要内容,如果未能解决你的问题,请参考以下文章

这个使用 PHP $$ 语法的循环是做啥的?

for(int i : x) 是做啥的? [复制]

CSS:这个星号 (*) 是做啥的?

python中的KFold到底是做啥的?

我不明白这个代码行是做啥的,$this->link->query($query)? [复制]

Android Studio中的View是做啥的?