以对角线顺序遍历矩阵

40

我原以为这个问题有一个简单的解决方案,只需要几个for循环和一些花哨的计数器,但显然它要复杂得多。

我的问题是,你会怎样编写一个(用C语言实现的)函数来遍历正方形矩阵的对角线条带。

示例:

1  2  3
4  5  6
7  8  9

需要按照以下顺序遍历:

[1],[2,4],[3,5,7],[6,8],[9]

每个条带上方都由方括号括起来。 其中一个要求是能够区分条带。这意味着你知道何时开始一个新的条带。因为还有另一个函数,我必须为条带中的每个项目以及在新条带开始之前调用它。因此,一个没有代码重复的解决方案是理想的。


不是,但它是我正在尝试为个人项目解决的问题的一部分。 - alyx
作为一个C风格的单维整型数组。 - alyx
17个回答

67

这里有些东西可以帮到你。只需要将printf替换为实际想要执行的操作即可。

#include <stdio.h>

int main()
{
    int x[3][3] = {1, 2, 3,
                   4, 5, 6,
                   7, 8, 9};
    int n = 3;
    for (int slice = 0; slice < 2 * n - 1; ++slice) {
        printf("Slice %d: ", slice);
        int z = (slice < n) ? 0 : slice - n + 1;
        for (int j = z; j <= slice - z; ++j) {
            printf("%d ", x[j][slice - j]);
        }
        printf("\n");
    }
    return 0;
}

输出:

Slice 0: 1
Slice 1: 2 4
Slice 2: 3 5 7
Slice 3: 6 8
Slice 4: 9

1
这是DRY的,但是并不容易理解。 - abyx
漂亮的逻辑!如果你想遍历矩阵的反面,即从矩阵的右上角开始,请将slice-j更改为(n-1)-slice-j。 - vprajan
2
@程序员,我不知道哪种情况对你不起作用了... 你错过括号了吗? 它是(n-1)-(slice-j),slice始终>0.. 在这里检查:http://analgorithmaday.blogspot.com/2011/04/traverse-array-diagonally.html - vprajan

46

我会像这样移动行:

1  2  3  x  x
x  4  5  6  x
x  x  7  8  9

只需遍历列即可。实际上,这可以在不进行物理移动的情况下完成。


2
没有物理移动//实现方式是什么? - Evgeni Nabokov

24

让我们来看一下矩阵元素的索引方式。

(0,0)   (0,1)   (0,2)   (0,3)   (0,4)  
(1,0)   (1,1)   (1,2)   (1,3)   (1,4)  
(2,0)   (2,1)   (2,2)   (2,3)   (2,4)  

现在,让我们来看一下条纹:
Stripe 1: (0,0)
Stripe 2: (0,1)    (1,0)  
Stripe 3: (0,2)    (1,1)    (2,0)
Stripe 4: (0,3)    (1,2)    (2,1)
Stripe 5: (0,4)    (1,3)    (2,2)
Stripe 6: (1,4)    (2,3)
Stripe 7: (2,4)

如果你仔细观察,你会注意到一件事情。每个条纹中每个矩阵元素的索引之和是恒定的。所以,这里是实现这个功能的代码。

public static void printSecondaryDiagonalOrder(int[][] matrix) {
    int rows = matrix.length;
    int cols = matrix[0].length;
    int maxSum = rows + cols - 2;

    for (int sum = 0; sum <= maxSum; sum++) {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (i + j - sum == 0) {
                    System.out.print(matrix[i][j] + "\t");
                }
            }
        }
        System.out.println();
    }
}

它不是最快的算法(需要执行(rows * cols * (rows+cols-2))次操作),但其背后的逻辑非常简单。


因为解释得非常清晰易懂,所以点赞了。 - Safin Ghoghabori

5

我在这里找到了这个链接:如何遍历矩阵的对角线条带

#include <stdio.h>

int main()
{
    int x[3][4] = { 1,  2,  3,  4,
                    5,  6,  7,  8,
                    9, 10, 11, 12};
    int m = 3;
    int n = 4;
    for (int slice = 0; slice < m + n - 1; ++slice) {
        printf("Slice %d: ", slice);
        int z1 = slice < n ? 0 : slice - n + 1;
        int z2 = slice < m ? 0 : slice - m + 1;
        for (int j = slice - z2; j >= z1; --j) {
                printf("%d ", x[j][slice - j]);
        }
        printf("\n");
    }
    return 0;
}

输出:

Slice 0: 1
Slice 1: 5 2
Slice 2: 9 6 3
Slice 3: 10 7 4
Slice 4: 11 8
Slice 5: 12

我发现这种方法非常优雅,因为它只需要两个附加变量(z1和z2)的内存,这些变量基本上保存有关每个片段长度的信息。外循环移动到切片编号(slice),然后内循环通过索引移动到每个切片:slice - z1 - z2。然后您需要的所有其他信息是算法从哪里开始以及如何在矩阵中移动。在前面的示例中,它将首先向下移动矩阵,达到底部之后,将向右移动:(0,0) -> (1,0) -> (2,0) -> (2,1) -> (2,2) -> (2,3)。同样,此模式由变量z1和z2捕获。行增量与slice号一起递增,直到达到底部,然后z2将开始增加,可以用于使行索引保持在其位置常数:slice-z2。每个切片的长度可由以下公式得知: slice-z1-z2,执行以下操作:(slice - z2) - (slice - z1 -z2)(减法是因为算法按升序m--,n ++移动),结果为z1,这是内部循环的停止准则。只剩下列索引,这个索引方便地从j在到达底部后保持不变而继承,之后开始递增。

前面的算法仅按从左到右的升序移动,从左上角(0,0)开始。当我需要此算法时,我还需要按降序从矩阵底部左侧(m,n)开始搜索矩阵。因为我对算法非常着迷,所以决定深入了解并进行修改:

  • 切片长度再次由以下公式得知:slice-z1-z2
  • 片段的起始位置是:(2,0) -> (1,0) -> (0,0) -> (0,1) -> (0,2) -> (0,3)
  • 每个片段的移动是m ++和n ++

我发现将其描述如下很有用:

  • slice=0 z1=0 z2=0 (2,0) (列索引= 行索引 - 2)
  • slice=1 z1=0 z2=0 (1,0) (2,1) (列索引= 行索引 - 1)
  • slice=2 z1=0 z2=0 (0,0) (1,1) (2,2) (列索引= 行索引 + 0)
  • slice=3 z1=0 z2=1 (0,1) (1,2) (2,3) (列索引= 行索引 + 1)
  • slice=4 z1=1 z2=2 (0,2) (1,3) (列索引= 行索引 + 2)
  • slice=5 z1=2 z2=3 (0,3) (列索引= 行索引 + 3)

推导如下: j = (m-1) - slice + z2 (使用slice长度的表达式作为停止条件:((m-1) - slice + z2)+(slice -z2 - z1))结果为:(m-1) - z1 现在我们有了内部循环的参数:for (int j = (m-1) - slice + z2; j < (m-1) - z1; j++)

行索引由j确定,我们知道当j开始保持不变时,列索引才开始递增,因此再次在表达式中加入j并不是个坏主意。通过上面总结的求和结果之间的差异,我注意到差异总是等于j - (slice - m +1),将其用于一些其他情况的测试后,我相信它适用于所有情况(我不是数学家;P),因此从左下方开始下降运动的算法如下:

#include <stdio.h>

int main()
{
    int x[3][4] = { 1,  2,  3,  4,
                    5,  6,  7,  8,
                    9, 10, 11, 12};
    int m = 3;
    int n = 4;
    for (int slice = 0; slice < m + n - 1; ++slice) {
        printf("Slice %d: ", slice);
        int z1 = slice < n ? 0 : slice - n + 1;
        int z2 = slice < m ? 0 : slice - m + 1;
        for (int j = (m-1) - slice + z2; j <= (m-1) - z1; j++) {
                printf("%d ", x[j][j+(slice-m+1)]);
        }
        printf("\n");
    }
    return 0;
}

现在我把另外两个方向留给你(仅当顺序真正重要时才重要)。这个算法非常具有挑战性,即使你认为自己知道它是如何工作的,它仍然会让你措手不及。但我认为它非常美丽,因为它确实像你所期望的那样在矩阵中移动。如果有人了解更多关于该算法的信息,例如名称,那么我可以看看我在这里做的事情是否合理,或许有更好的解决方案。

解释得很好,但还有一些改进的空间。例如:“在前面的例子中,它会移动......底部它会向右移动”,这是错误的,因为算法在每一步中并没有穿过矩阵,就像你使用“它会移动”的措辞所暗示的那样。相反,你应该提到它在每个切片增量时通过对角线切片,然后向下移动。这只是一个小改变,但可能会导致严重的误导。此外,我认为你在第二部分做了太多的工作。你只需要在相应的索引中引入(m-1)或(n-1)来反转方向即可。 - Alpha Mineron

4
我认为这可以成为任何类型矩阵的解决方案。
#include <stdio.h>

#define M 3
#define N 4

main(){
         int a[M][N] = {{1, 2, 3, 4}, 
                        {5, 6, 7, 8}, 
                        {9,10,11,12}};

         int i, j, t;
         for( t = 0; t<M+N; ++t)
              for( i=t, j=0; i>=0 ; --i, ++j)
                     if( (i<M) && (j<N) )
                             printf("%d ", a[i][j]);
         return 0;
}

“任何类型的矩阵”是什么意思?您能详细说明一下吗? - Safin Ghoghabori

2

// 这个算法适用于所有大小的矩阵。 ;)

    int x = 0;
    int y = 0;        
    int sub_x;
    int sub_y;

    while (true) {

        sub_x = x;
        sub_y = y;

        while (sub_x >= 0 && sub_y < y_axis.size()) {

            this.print(sub_x, sub_y);
            sub_x--;
            sub_y++;

        }

        if (x < x_axis.size() - 1) {

            x++;

        } else if (y < y_axis.size() - 1) {

            y++;

        } else {

            break;

        }

    }

1
感谢您没有假设宽度和高度总是相同的! - sharoz

2

我以为这个问题有一个简单的解决方案,几个for循环和一些花哨的计数器就可以了。

确切地说。

重要的是要注意,如果给每个项目分配一个索引 (i, j),那么在同一对角线上的项目具有相同的值j+ni,其中n是矩阵的宽度。因此,如果按照通常的方式遍历矩阵(即通过嵌套循环遍历ij),则可以使用上述方式访问数组中的对角线。


1
public void printMatrix(int[][] matrix) {
    int m = matrix.length, n = matrix[0].length;
    for (int i = 0; i < m + n - 1; i++) {
         int start_row = i < m ? i : m - 1;
         int start_col = i < m ? 0 : i - m + 1;
         while (start_row >= 0 && start_col < n) {
               System.out.print(matrix[start_row--][start_col++]);
         }
         System.out.println("\n")
     }
}

需要更多说明。 - Ashkan S
5
代码片段也许能解决问题,但请添加解释说明你的代码是如何回答这个问题的。 - anon

1

关键是迭代第一行中的每个项目,并从它向下走对角线。然后迭代最后一列中的每个项目(不包括我们在上一步中经过的第一个项目),然后沿其对角线向下。

这里是源代码,假设矩阵是一个方阵(未经测试,从工作的Python代码翻译而来):

#define N 10
void diag_step(int[][] matrix) {
    for (int i = 0; i < N; i++) {
        int j = 0;
        int k = i;
        printf("starting a strip\n");
        while (j < N && i >= 0) {
            printf("%d ", matrix[j][k]);
            k--;
            j++;
        }
        printf("\n");
    }

    for (int i = 1; i < N; i++) {
        int j = N-1;
        int k = i;
        printf("starting a strip\n");
        while (j >= 0 && k < N) {
            printf("%d ", matrix[k][j]);
            k++;
            j--;
        }
        printf("\n");
    }   
}   

1

伪代码:

N = 2 // or whatever the size of the [square] matrix
for x = 0 to N
  strip = []
  y = 0
  repeat
     strip.add(Matrix(x,y))
     x -= 1
     y -= 1
  until x < 0
  // here to print the strip or do some' with it

// And yes, Oops, I had missed it... 
// the 2nd half of the matrix...
for y = 1 to N    // Yes, start at 1 not 0, since main diagonal is done.
   strip = []
   x = N
   repeat
      strip.add(Matrix(x,y))
      x -= 1
      y += 1
   until x < 0
  // here to print the strip or do some' with it

(假设x索引行,y索引列,如果矩阵索引方式相反,则反转这两个)


1
这似乎没有涵盖矩阵的后半部分。 - Mark Byers
@Mark B 谢谢你的注意!这证明了没有什么是完全琐碎的,我太马虎了! - mjv

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