自下而上的集合生成和排序

11

如果你知道任何与数值方法相关的方法,请在此处发布!

背景

我有一个包含每个集合值的数组values,每个值的索引对应于该值绑定到的集合,因此我将集合表示为整数,其中元素表示位位置,例如:一个具有其元素为一的集合表示为...001,其中1LSB

因此,集合仅是索引,从不存储,它是即时生成的密钥,导致数组中代表集合值的索引。

我的目标是给定一个集合,是否有任何成对不相交子集的总和大于该集合的值。例如,如果集合0111的值为3,其中两个子集的值分别为0100=20011=2,则更有利于进行此分割。我会为集合的所有子集执行此操作。

给定三个代理和排序是集合编号表示。

val[8] = {0,1,2,4,3,2,4,2} the values is not important, only how they are ordered
          0 0 0 0 1 1 1 1 MSB bit representation of the index
          0 0 1 1 0 0 1 1
          0 1 0 1 0 1 0 1 LSB

最佳拆分111的方式是011和100,总和为7。因此要获取只包含第一个元素的集合(即001)的值,可以将val[1]赋值给它,要获取包含元素1和3(101)的集合的值,则可以将val[5]赋值给它。

当按基数进行分组时,val数组的顺序是如何排序的。

val[8] = {0,1,2,3,4,2,4,2}
          0 0 0 1 0 1 1 1 MSB bit representation of the index
          0 0 1 0 1 0 1 1
          0 1 0 0 1 1 0 1 LSB

在这里,您需要将索引翻译为数组中的正确二进制位,所以对于只有第三个元素(100)的集合,它看起来像这样:val[translate(4)]。考虑到数组大小> 2^25个元素。

请参阅Improving random memory access when random access is needed以获得更多澄清。

然而,这会导致内存中高度随机访问的顺序,即使我按基数分组它们。当前按基数分组,并生成索引比按表示集合的数字排序要慢。

我使用常量内存中的帕斯卡三角形来生成按基数分组的集合的索引,如Determin the lexicographic distance between two integers中的答案所述。

当基数为四个代理时,集合值组成的位置

n index 1  2  4  8     3  5  6  9  10 12    7  11 13 14    15
        -----------------------------------------------------
MSB     0  0  0  1  |  0  0  0  1  1  1  |  0  1  1  1  |  1
        0  0  1  0  |  0  1  1  0  0  1  |  1  0  1  1  |  1
        0  1  0  0  |  1  0  1  0  1  0  |  1  1  0  1  |  1
LSB     1  0  0  0  |  1  1  0  1  0  0  |  1  1  1  0  |  1

一个索引表示它在无序的基数中的索引位置。这只是为了显示每个集合的值所在的位置。

整数集合表示值数组中的索引,可以通过直接索引(我目前正在执行的操作,提供随机访问)或通过将集合转换为索引来实现。

想法

我想到了自下而上生成集合的方法,而不是将集合分成子集。例如,我会从集合 {0100,0011},{0010,0101},{0001,0110} 来生成 0111,而不是将其分割为所有成对不相交的子集。

如何以及为什么它应该工作

假设我们想要评估具有基数3的集合的所有分裂,即集合 7,11,13,14。由于分裂基数为3的集合的唯一方式是将其分裂为基数为1和2的集合,因此我们需要评估基数为1和2的所有不相交子集的总和是否大于这些集合的并集。

所需符号(可能有点缺陷):

|C|=n,∀ a,b : a ∪ b = C , a ∩ b ={Ø}, |a|+|b| = n

因此,通过使用对每个线程进行合并的内存访问来读取值,对于所有形成基数为n的集合的子集,请检查其值是否大于形成的集合。如果是,则更新该值。

简单的例子,如果 n=2,则应读取所有基数为1的值,并执行这些集合的所有组合并相应地更新。这个例子很容易,因为所有集合互不重叠:

pseudo code for 4 threads, input card1 is pointer to array of sets |s| =1
__shared__ int value[4];
tid = threadIdx.x;
value[tid] = card1[tid]; // coalesced memory access
int thvalue = value[tid]; // holds the value for the thread, to avoid bank conflict
int rvalue[blockDim.x/2]= 0; //holds the sum
int i = blockDim.x;
int x = 0;
//reduction loop that dont generate duplicate sets
for(;i>0;i>>=1) {
    if(tid < i) {
        x++;
        rvalue[x-1] = value[(tid+x)%blockDim.x] + thvalue; 
    }
}
for(i = 0; i < x; i++) {
    int index = getindex(tid,i,1); //gets the index for the set it generated, 1 represent the cardinality
    if(output[index] < rvalue[i])
        output[index] = rvalue[i];
}

缩减循环的迭代

Thread set specific for thread  first iteration second iteration 
0      0001                     0001 + 0010     0001 + 0100
1      0010                     0010 + 0100     0010 + 1000
2      0100                     0100 + 1000     none
3      1000                     1000 + 0001     none

正如你所看到的,它已经获取了所有形成基数为2的集合子集的值。

问题在于,生成基数大于2的集合更加棘手,因为并非所有集合都是不相交的。例如0001和0011不相交。

请记住,我没有在任何地方存储集合,只有集合的值。

最终

考虑到这一点,如何创建一种算法,从不相交的子集中读取内存联合,并生成所有集合。没有检查子集是否不相交,它应该是完全确定性的。

赏金

该算法应该用明显的步骤描述文本或伪代码。

它应该通过示例证明它的有效性。请注意,这个算法可以达到n^32个集合,因此需要很好的可扩展性。

该算法可以分为两个或多个实例,例如一个偶数和一个奇数。

如果您认为您已经有了一个即使有很多这样的指令也可以,请尝试发表,我会非常感激任何信息。

如果以另一种方式排序,但仍然按照我所描述的方式工作,则请发布它,任何帮助都很有用

如果有任何不清楚的地方,请询问。

简洁解释

我有一个带有值的数组Z,索引i(例如Z[i])表示一个整数集合,根据Z的排序方式,值按基数分组,并按二进制词典排序排列-> 集合值所在的位置1,2,4,3,5,6,7 <- 因此我使用一个函数(我已经实现了这个函数),将索引转换为正确的索引。例如,集合3->索引4。

通过将集合按基数分组的方式,我想确定是否任何两个不相交的集合的值大于它们形成的集合。

例如|a| = 3,|b|+|c| =3,b ∩ c ={Ø},|b| =1因此读取类型为bcX数量的值,查找所有不相交子集的bc类型(基数为3的集合)并获取它们的和。继续直到所有集合都被“生成”。

参考资料

基于汉明权重的索引

确定两个整数之间词典距离

在需要随机访问时提高随机内存访问性能


我可能只是错过了,但集合的值是如何确定的?(例如0111的值为3)。随机访问、基数等是否都属于另一个问题的一部分?(也就是说,对于这个问题,您只需要一个算法,假设位字符串的值具有足够短的时间查找(显然,您最好能解决两个问题))。 - Bernhard Barker
@Dukeling 我已经更新了我的文本,但是这些值是预先确定的,我所做的就是评估任何一组分裂是否比该组本身更有利。目前我拥有的功能良好,只是随机访问非常糟糕。我想要的是一个算法,它可以执行伪代码所做的所有操作,但适用于所有基数的集合。因此,需要一种算法以合并方式提取与已知基数集合相关联的值,并计算形成基数更大的集合的两个集合的所有总和。因此,它可以读取基数为1和2的集合,以形成基数为3的集合。 - 1-----1
2个回答

1
我不知道这是否对您有所帮助,但我在《黑客的宝典》中找到了一个无分支计算字中所有1位的函数,看起来可能有助于您确定集合的基数:
int pop(unsigned int x) {
    x = x - ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x0000003F;
}

在文本中,沃伦声称上述序列可以编译为仅有21个指令。但是,在我的i7开发机上使用MSVC 2010检查了该函数的反汇编代码,并发现实际计算的指令大约为22条,总共为33条(包括堆栈操作)。在现代CPU或GPU上,它应该很快,因为它没有分支。

CUDA有一个__popc指令用于计算人口数量,我正在寻找的是一种算法,可以进行自下而上的集合生成,并对生成集合的子集中的值进行评估。但感谢您的考虑。 - 1-----1

1

尝试利用三进制编号

对于术语,我称“值”为您的集合估值函数,“目标”为您的目标函数,即在每个二进制分区上求和值的最大值。

将二进制数B分成两个不相交的部分L和R的每个分裂都可以用三进制数C表示,其中

B = L | R   (bitwise OR)
L ^ R = 0   (bitwise XOR)

C[i] = 0 means B[i] = 0 and L[i] = R[i] = 0
C[i] = 1 means B[i] = 1 and L[i] = 1
C[i] = 2 means B[i] = 2 and R[i] = 1

然后,在三进制中简单地枚举从1到3 ** n的数字:例如(n = 3):000、001、002、010、011、012、020等。

好吧,实际上,如果你手头只有二进制,高效地进行三进制计数并不完全简单。但请相信我,我会在介绍高级算法之后解释这一点...

所以你按顺序用三进制计数,并给定一个三进制数C,你可以获得L和R - 怎么做?我也会在下面解释,相信我 :)

给定L和R,现在您可以查找L和R处的估值,并更新B处的目标:target[B] = max(val[L],val[R])。

好的,这就是高级算法。我无法在短时间内证明它,但它似乎具有非常好的缓存局部性属性。换句话说,value[L]和value[R]将倾向于同时存在于少量缓存行中。 此外,我认为并行化的最佳选择是将 分成模3的值,或模9的值等。

二进制中的高效三进制计数

如何高效地进行三进制计数?尝试以下方法:使用四进制计数,并跳过一些数字。

换句话说,一个三进制数字将由两个二进制位表示,我们将禁止组合11的出现。

 repr | value
 0 0  | 0
 0 1  | 1
 1 0  | 2
 1 1  | *undefined*

现在,我们如何高效地知道何时跳过?嗯,增量的模式很容易弄清楚:

1 1 2 1 1 2 1 1 6 1 1 2 1 1 2 1 1 6 1 1 2 1 1 2 1 1 22 1 1 2 ...

我的建议是预先计算一个大小为3的幂(例如3 ** 7 = 2187)的大块,并且偶尔动态计算第n个3的幂[提示:它与n的立方有关..]。

所以你从00.00.00开始。你加1得到00.00.01。你再加1得到00.00.10。现在你必须加2才能跳过11组合,这让你得到00.01.00。等等。

如何从C获取L和R

现在,在我们的三进制四元表示中,C实际上只是L和R交错。为了有效地获取L和R,您可以查看this S/O question的答案或应用其他位操作技巧。

顺便说一句

总的来说,我不确定我们是否真正使用了3进制或4进制。嗯...

祝您玩得愉快,好运!


1
顺便提一下,如果您需要高效的模3运算,可以利用2 mod 3 == -1 mod 3这个事实,因此配方是:将输入数字解交错为偶数位和奇数位,并减去位种群计数。 - spam_eggs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接