4

我有一个沿对角线存储在平面缓冲区中的二维矩阵。例如,一个 4x4 矩阵的索引会像这样分散:

 0   2   5   9
 1   4   8  12
 3   7  11  14
 6  10  13  15

使用这种表示,在给定原始索引和 X/Y 偏移量的情况下,计算相邻元素索引的最有效方法是什么?例如:

// return the index of a neighbor given an offset
int getNGonalNeighbor(const size_t index,
                      const int x_offset,
                      const int y_offset){
    //...
}

// for the array above:
getNGonalNeighbor(15,-1,-1); // should return 11
getNGonalNeighbor(15, 0,-1); // should return 14
getNGonalNeighbor(15,-1, 0); // should return 13
getNGonalNeighbor(11,-2,-1); // should return 1

我们在这里假设永远不会发生溢出并且没有回绕。

我有一个涉及很多三角数的解决方案和三角根计算的解决方案。它还包含很多分支,如果可能的话,我更愿意用代数代替(这将在分散控制流很昂贵的 GPU 上运行)。我的解决方案有效,但非常冗长。我觉得必须有一种更简单且计算密集度更低的方式来做到这一点。

如果有人可以为这个特定的问题/表示命名,也许会对我有所帮助。

如果有人感兴趣,我可以发布我的完整解决方案,但正如我所说,对于这样一个简单的任务来说,它很长而且相对复杂。简而言之,我的解决方案是:

  • 将原始索引转换为更大的三角矩阵以避免处理 2 个三角形(例如 13 将变为 17)

对于 4x4 矩阵,这将是:

0   2   5   9   14  20  27
1   4   8   13  19  26  
3   7   12  18  25  
6   11  17  24  
10  16  23
15  22  
21  
  • 使用偏移量的曼哈顿距离和索引的三角根计算此表示中邻居对角线的索引。
  • 使用偏移量计算邻居在此对角线中的位置
  • 通过删除填充转换回原始表示。

出于某种原因,这是我能想到的最简单的解决方案。

编辑:

有循环来累积偏移量

我意识到考虑到三角形数字的属性,将矩阵分成两个三角形会更容易(让我们称之为 0 到 9 的“上三角形”和 10 到 15 的“下三角形”)并在里面有一个带有测试的循环通过在上三角形中加一并在下三角形中减去一来累积偏移量(如果有意义的话)。但是对于我的解决方案,必须不惜一切代价避免循环,尤其是行程计数不平衡的循环(再次,对 GPU非常不利)。

所以我更多地寻找代数解决方案而不是算法解决方案

构建查找表:

同样,由于 GPU,最好避免构建查找表并在其中进行随机访问(非常昂贵)。代数解是优选的。

矩阵的性质

  • 矩阵的大小是已知的。
  • 现在我只考虑方阵,但矩形矩阵的解决方案也很好。
  • 正如我的示例中的函数名称所暗示的那样,将解决方案扩展到 N 维体积(因此 N 边展平)也将是一个很大的优势。
4

2 回答 2

1

查表

#include <stdio.h>

#define SIZE 16
#define SIDE  4  //sqrt(SIZE)

int table[SIZE];
int rtable[100];// {x,y| x<=99, y<=99 }

void setup(){
    int i, x, y, xy, index;//xy = x + y

    x=y=xy=0;
    for(i=0;i<SIZE;++i){
        table[i]= index= x*10 + y;
        rtable[x*10+y]=i;
        x = x + 1; y = y - 1;//right up
        if(y < 0 || x >= SIDE){
            ++xy;
            x = 0;
            y = xy;;
            while(y>=SIDE){
                ++x;
                --y;
            }
        }
    }
}
int getNGonalNeighbor(int index, int offsetX, int offsetY){
    int x,y;
    x=table[index] / 10 + offsetX;
    y=table[index] % 10 + offsetY;
    if(x < 0 || x >= SIDE || y < 0 || y >= SIDE) return -1; //ERROR
    return rtable[ x*10+y ];
}

int main() {
    int i;
    setup();
    printf("%d\n", getNGonalNeighbor(15,-1,-1));
    printf("%d\n", getNGonalNeighbor(15, 0,-1));
    printf("%d\n", getNGonalNeighbor(15,-1, 0));
    printf("%d\n", getNGonalNeighbor(11,-2,-1));
    printf("%d\n", getNGonalNeighbor(0, -1,-1));

    return 0;
}

不要使用表格版本。

#include <stdio.h>

#define SIZE 16
#define SIDE  4

void num2xy(int index, int *offsetX, int *offsetY){
    int i, x, y, xy;//xy = x + y

    x=y=xy=0;
    for(i=0;i<SIZE;++i){
        if(i == index){
            *offsetX = x;
            *offsetY = y;
            return;
        }
        x = x + 1; y = y - 1;//right up
        if(y < 0 || x >= SIDE){
            ++xy;
            x = 0;
            y = xy;;
            while(y>=SIDE){
                ++x;
                --y;
            }
        }
    }
}
int xy2num(int offsetX, int offsetY){
    int i, x, y, xy, index;//xy = x + y

    x=y=xy=0;
    for(i=0;i<SIZE;++i){
        if(offsetX == x && offsetY == y) return i;
        x = x + 1; y = y - 1;//right up
        if(y < 0 || x >= SIDE){
            ++xy;
            x = 0;
            y = xy;;
            while(y>=SIDE){
                ++x;
                --y;
            }
        }
    }
    return -1;
}
int getNGonalNeighbor(int index, int offsetX, int offsetY){
    int x,y;

    num2xy(index, &x, &y);

    return xy2num(x + offsetX, y + offsetY);
}

int main() {
    printf("%d\n", getNGonalNeighbor(15,-1,-1));
    printf("%d\n", getNGonalNeighbor(15, 0,-1));
    printf("%d\n", getNGonalNeighbor(15,-1, 0));
    printf("%d\n", getNGonalNeighbor(11,-2,-1));
    printf("%d\n", getNGonalNeighbor(0, -1,-1));

    return 0;
}
于 2013-04-17T22:41:03.520 回答
1

实际上,我已经在我的代码中的其他地方拥有了解决它的元素。正如 BLUEPIXY 的解决方案所暗示的那样,我正在使用分散/聚集操作,我已经为布局转换实现了这些操作。

该解决方案基本上重建了(x,y)矩阵中给定元素的原始索引,应用索引偏移并将结果转换回转换后的布局。它将正方形分成 2 个三角形,并根据它属于哪个三角形来调整计算。

这是一个几乎完全代数转换:它不使用循环和表查找,内存占用少,分支少。代码可能可以进一步优化。

这是代码的草稿:

#include <stdio.h>
#include <math.h>

// size of the matrix
#define SIZE 4

// triangle number of X
#define TRIG(X) (((X) * ((X) + 1)) >> 1)
// triangle root of X
#define TRIROOT(X) ((int)(sqrt(8*(X)+1)-1)>>1);

// return the index of a neighbor given an offset
int getNGonalNeighbor(const size_t index,
                      const int x_offset,
                      const int y_offset){
    // compute largest upper triangle index
    const size_t upper_triangle = TRIG(SIZE);

    // position of the actual element of index
    unsigned int x = 0,y = 0;

    // adjust the index depending of upper/lower triangle.
    const size_t adjusted_index = index < upper_triangle ?
                index :
                SIZE * SIZE - index - 1;

    // compute triangular root
    const size_t triroot = TRIROOT(adjusted_index);
    const size_t trig = TRIG(triroot);
    const size_t offset = adjusted_index - trig;

    // upper triangle
    if(index < upper_triangle){
        x = offset;
        y = triroot-offset;
    }
    // lower triangle
    else {
        x = SIZE - offset - 1;
        y = SIZE - (trig + triroot + 1 - adjusted_index);
    }

    // adjust the offset
    x += x_offset;
    y += y_offset;

    // manhattan distance
    const size_t man_dist = x+y;

    // calculate index using triangular number
    return TRIG(man_dist) +
            (man_dist >= SIZE ? x - (man_dist - SIZE + 1) : x) -
            (man_dist > SIZE ? 2* TRIG(man_dist - SIZE) : 0);
}

int main(){
    printf("%d\n", getNGonalNeighbor(15,-1,-1)); // should return 11
    printf("%d\n", getNGonalNeighbor(15, 0,-1)); // should return 14
    printf("%d\n", getNGonalNeighbor(15,-1, 0)); // should return 13
    printf("%d\n", getNGonalNeighbor(11,-2,-1)); // should return 1
}

输出确实是:

11
14
13
1

如果你认为这个解决方案看起来过于复杂和低效,我提醒你,这里的目标是 GPU,与内存访问相比,计算成本几乎为零,并且所有索引计算都是使用大规模并行架构同时计算的。

于 2013-04-18T00:22:28.417 回答