这个基数排序代码中的最后一个“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”循环是做啥的?的主要内容,如果未能解决你的问题,请参考以下文章