【问题标题】:Neighbor index computation for diagonally flattened matrix对角展平矩阵的邻域索引计算
【发布时间】:2013-04-17 20:17:58
【问题描述】:

我有一个沿对角线存储在平面缓冲区中的二维矩阵。例如,一个 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

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

我有一个涉及大量triangular number 和三角根计算的解决方案。它还包含许多分支,如果可能的话,我更愿意用代数代替(这将在分散控制流很昂贵的 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 边形展平)也将是一大优势。

【问题讨论】:

  • 查看模式:0 2 将始终是第一行中的第一个元素,然后对于每个 (0,j) s.t. j 大于 1,它增加了 +1( 2+3 =5, +4 =9, +5 = 14...) 与第一列相同。再想一想,在查看列号和移动column-column时给出了模式。生病尝试更好地解释:0,1,3,6,10 每次增加 x+1。对于第 1 列(以 2 开头),您以 +2、+3、+4 开头,每列都相同
  • 您似乎在描述上三角形的行为(在这种情况下,索引小于 9)。问题是下三角形会发生变化,如果索引在一个三角形中而邻居在另一个三角形中,则会变得混乱。这就是为什么我将整个矩阵包装在一个三角形矩阵中并使用您描述的属性。
  • 我正在查看问题开头的 4x4 矩阵。
  • 上三角和下三角我的意思是如果你把矩阵分成两个三角形:从 0 到 9 和 10 到 15。你所描述的看起来像三角形数字序列,当你从一个开始时它会中断这些三角形的另一个。以 10 为例,上面的元素是 7,它在第 4 条对角线上,但 10-4=6。
  • 所以在对角线之后它反转并与 x-1 一起

标签: c++ c multidimensional-array indexing diagonal


【解决方案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;
}

【讨论】:

  • @Thibaut 没错。桶大小表示原始坐标而不是偏移量。
  • 谢谢,这会奏效。然而,查找表的有效负载似乎与原始矩阵一样大。实际上,这几乎就像将矩阵转置到另一个布局一样。这绝对是一个进步,但我正在寻找一个更代数的解决方案。
  • 另外,为您提供我正在寻找的解决方案的更多背景信息:这是用于 GPGPU 编程,其中内存中的随机访问非常昂贵,而不是“对齐”(合并)访问。这就是我首先使用这种布局的原因:对于我正在解决的问题,对于传统布局,内存访问将是随机的,但对于我在这里使用的布局则不然。但是,您的查找表通过查找表重新引入了这种随机访问。
  • 第二个解决方案很有趣。不过,它看起来仍然很像布局转换。您的num2xy 和xy2num 似乎是变相的分散/收集操作。这很有趣,因为它们具有代数等价物,可以摆脱所有这些循环和分支。我一定会深入挖掘,谢谢。
  • 我认为num2xy和xy2num可以,也可以改变搜索基点的形状(例如) 15.我不知道一点代数函数。
【解决方案2】:

我实际上已经在我的代码中的其他地方有了解决它的元素。正如 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,与内存访问相比,GPU 的计算成本几乎为零,并且所有索引计算都是使用大规模并行架构同时计算的。

【讨论】:

  • 是的,除非有人可以提出更简单的解决方案,涉及更少的计算。
猜你喜欢
  • 2022-11-21
  • 2015-04-27
  • 2017-08-12
  • 2013-06-15
  • 1970-01-01
  • 2020-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多