【问题标题】:Fill Matrix in Spiral Form from center从中心以螺旋形式填充矩阵
【发布时间】:2021-12-28 04:01:49
【问题描述】:

我最近完成了为我正在进行的项目制作的算法。

简单来说,我项目的一部分需要填充一个矩阵,具体怎么做的要求是:

- Fill the matrix in form of spiral, from the center.
- The size of the matrix must be dynamic, so the spiral can be large or small.
- Every two times a cell of the matrix is filled, //DO STUFF must be executed.

最后,我编写的代码有效,这是我的最大努力,我无法对其进行更多优化,不得不使用这么多 if 让我有点困扰,我想知道是否有人可以接受查看我的代码,看看是否可以进一步优化它或一些建设性的评论(它运作良好,但如果它更快会更好,因为这个算法将在我的项目中执行多次)。也方便其他人使用!

#include <stdio.h>

typedef unsigned short u16_t;
const u16_t size = 7; //<-- CHANGE HERE!!! just odd numbers and bigger than 3
const u16_t maxTimes = 2;
u16_t array_cont[size][size] = { 0 };

u16_t counter = 3, curr = 0;
u16_t endColumn = (size - 1) / 2, endRow = endColumn;
u16_t startColumn = endColumn + 1, startRow = endColumn + 1;
u16_t posLoop = 2, buffer = startColumn, i = 0;

void fillArray() {
    if (curr < maxTimes) {
        if (posLoop == 0) { //Top
            for (i = buffer; i <= startColumn && curr < maxTimes; i++, curr++)
                array_cont[endRow][i] = counter++;
            if (curr == maxTimes) {
                if (i <= startColumn) {
                    buffer = i;
                } else {
                    buffer = endRow;
                    startColumn++;
                    posLoop++;
                }
            } else {
                buffer = endRow;
                startColumn++;
                posLoop++;
                fillArray();
            }
        } else if (posLoop == 1) { //Right
            for (i = buffer; i <= startRow && curr < maxTimes; i++, curr++)
                array_cont[i][startColumn] = counter++;
            if (curr == maxTimes) {
                if (i <= startRow) {
                    buffer = i;
                } else {
                    buffer = startColumn;
                    startRow++;
                    posLoop++;
                }
            } else {
                buffer = startColumn;
                startRow++;
                posLoop++;
                fillArray();
            }
        } else if (posLoop == 2) { //Bottom
            for (i = buffer; i >= endColumn && curr < maxTimes; i--, curr++)
                array_cont[startRow][i] = counter++;
            if (curr == maxTimes) {
                if (i >= endColumn) {
                    buffer = i;
                } else {
                    buffer = startRow;
                    endColumn--;
                    posLoop++;
                }
            } else {
                buffer = startRow;
                endColumn--;
                posLoop++;
                fillArray();
            }
        } else if (posLoop == 3) { //Left
            for (i = buffer; i >= endRow && curr < maxTimes; i--, curr++)
                array_cont[i][endColumn] = counter++;
            if (curr == maxTimes) {
                if (i >= endRow) {
                    buffer = i;
                } else {
                    buffer = endColumn;
                    endRow--;
                    posLoop = 0;
                }
            } else {
                buffer = endColumn;
                endRow--;
                posLoop = 0;
                fillArray();
            }
        }
    }
}

int main(void) {
    array_cont[endColumn][endColumn] = 1;
    array_cont[endColumn][endColumn + 1] = 2;
    //DO STUFF

    u16_t max = ((size * size) - 1) / maxTimes;
    for (u16_t j = 0; j < max; j++) {
        fillArray();
        curr = 0;
        //DO STUFF
    }

    //Demostration
    for (u16_t x = 0; x < size; x++) {
        for (u16_t y = 0; y < size; y++)
            printf("%-4d ", array_cont[x][y]);
        printf("\n");
    }

    return 0;
}

【问题讨论】:

    标签: c++ algorithm matrix spiral


    【解决方案1】:

    请注意,沿对角线的数字(1、9、25、49)是奇数的平方。这是一个重要的线索,因为它表明矩阵中心的 1 应该被视为螺旋的结束

    从每个螺旋的末端开始,x,y 坐标应该向上和向右调整 1。然后可以通过向下、向左、向上和向右移动相同的量来构建螺旋的下一层.

    例如,从1的位置开始,向上向右移动(到9的位置),然后形成一个循环,过程如下:

     move down, and place the 2  
     move down, and place the 3  
     move left, and place the 4  
     move left, and place the 5  
     etc.
    

    因此代码看起来像这样:

    int size = 7;
    int matrix[size][size];
    int dy[] = { 1,  0, -1, 0 };
    int dx[] = { 0, -1,  0, 1 };
    int directionCount = 4;
    
    int ringCount = (size - 1) / 2;
    int y = ringCount;
    int x = ringCount;
    int repeatCount = 0;
    int value = 1;
    
    matrix[y][x] = value++;
    for (int ring = 0; ring < ringCount; ring++)
    {
        y--;
        x++;
        repeatCount += 2;
        for (int direction = 0; direction < directionCount; direction++)
            for (int repeat = 0; repeat < repeatCount; repeat++)
            {
                y += dy[direction];
                x += dx[direction];
                matrix[y][x] = value++;
            }
    }
    

    【讨论】:

    • 您不应该在 C++ 中使用 VLA。 . . . .
    • 哈,我才意识到我们在哪里。螺旋矩阵是我最喜欢的 hack 之一,虽然不是在 C++ 中。在 Python 中,将矩阵“旋转”90 度相对较短。所以我做了旋转,添加边,旋转,添加边,旋转,添加边等。在 C++ 中,或者为了提高效率,我可能会像你一样做,虽然可能不是 dxdy 是数组,我会使用整数,并且仅在内部循环之后使用 C++ 的 dx, dy = -dy, dx 等效项更改它们。那会更快吗?我不知道 C++ 是否以某种方式优化了数组访问。
    • @KellyBundy 编译器可能优化数组方式,因为它有一些令人惊讶的优化。例如,请参阅我对this question 的回答。如果我要达到最高速度,我会首先检查编译器生成的程序集。如果它没有做任何惊人的事情,我会删除 direction 循环,然后将 repeat 循环复制四次,完全摆脱数组。 repeat 循环的第一个副本是 for (...) { matrix[++y][x] = value++; }。但通常最好让编译器完成它的工作。
    • 我刚找到你的 cmets。如果您在下面查看我的答案,您会发现一切都将在编译时完成。这会给你最大的速度。另外:通过一些简单的数学运算,您可以使用分析公式来获取行和列。无需考虑旋转、环、添加边和其他东西。 . .
    【解决方案2】:

    我已经看到了很多做螺旋式的方法。基本上都是按照路径画出来的。

    但是,你也可以想出一个分析螺旋计算公式。

    因此,遵循路径等没有递归或迭代解决方案。如果我们有流水号,我们可以直接计算矩阵中的索引。

    我将从笛卡尔坐标系中数学正方向(逆时针)的螺旋开始。我们将专注于 X 和 Y 坐标。

    我制作了一个简短的 Excel 并从中导出了一些公式。这是一张简短的图片:

    从要求我们知道矩阵将是二次的。这让事情变得更容易。有点棘手的是,要让矩阵数据对称。但是通过一些简单的公式,从图片中得出,这并不是一个真正的问题。

    然后我们可以用一些简单的语句计算 x 和 y 坐标。请参阅下面带有长变量名的示例程序以更好地理解。代码是使用一些逐步的方法来说明实现的。当然,它可以更容易地变得更紧凑。反正。让我们看看。

    #include <iostream>
    #include <cmath>
    #include <iomanip>
    
    int main() {
        // Show some example values
        for (long step{}; step < 81; ++step) {
    
            // Calculate result
            const long roundedSquareRoot = std::lround(std::sqrt(step));
            const long roundedSquare = roundedSquareRoot * roundedSquareRoot;
            const long distance = std::abs(roundedSquare - step) - roundedSquareRoot;
            const long rsrIsOdd = (roundedSquareRoot % 2);
    
            const long x = (distance + roundedSquare - step - rsrIsOdd) / (rsrIsOdd ? -2 : 2);
            const long y = (-distance + roundedSquare - step - rsrIsOdd) / (rsrIsOdd ? -2 : 2);
            // Show ouput
            std::cout << "Step:" << std::setw(4) << step << std::setw(3) << x << ' ' << std::setw(3) << y << '\n';
        }
    }
    

    所以,您看到我们确实有一个分析解决方案。给定任何数字,我们可以使用公式计算 x 和 y 坐标。很酷。

    在矩阵中获取索引只是添加一些偏移量。

    有了这些知识,我们现在可以轻松计算完整的矩阵。而且,由于根本不需要运行时活动,我们可以让编译器完成这项工作。我们将简单地对所有内容使用 constexpr 函数。

    然后编译器将在编译时创建这个矩阵。在运行时,什么都不会发生。

    请看一个非常紧凑的解决方案:

    #include <iostream>
    #include <iomanip>
    #include <array>
    
    constexpr size_t MatrixSize = 15u;
    
    using MyType = long;
    static_assert(MatrixSize > 0 && MatrixSize%2, "Matrix size must be odd and > 0");
    constexpr MyType MatrixHalf = MatrixSize / 2;
    
    using Matrix = std::array<std::array<MyType, MatrixSize>, MatrixSize >;
    
    // Some constexpr simple mathematical functions ------------------------------------------------------------------------------
    // No need for <cmath>
    constexpr MyType myAbs(MyType v) { return v < 0 ? -v : v; }
    constexpr double mySqrtRecursive(double x, double c, double p) {return c == p? c: mySqrtRecursive(x, 0.5 * (c + x / c), c); }
    constexpr MyType mySqrt(MyType x) {return (MyType)(mySqrtRecursive((double)x,(double)x,0.0)+0.5); }
    
    // Main constexpr function will fill the matrix with a spiral pattern during compile time -------------------------------------
    constexpr Matrix fillMatrix() {
        Matrix matrix{};
        for (int i{}; i < (MatrixSize * MatrixSize); ++i) {
            const MyType rsr{ mySqrt(i) }, rs{ rsr * rsr }, d{ myAbs(rs - i) - rsr }, o{ rsr % 2 };
            const size_t col{ (size_t)(MatrixHalf +((d + rs - i - o) / (o ? -2 : 2)))};
            const size_t row{ (size_t)(MatrixHalf -((-d + rs - i - o) / (o ? -2 : 2)))};
            matrix[row][col] = i;
        }
        return matrix;
    }
    // This is a compile time constant!
    constexpr Matrix matrix = fillMatrix();
    
    // All the above has been done during compile time! -----------------------------------------
    
    
    int main() {
    
        // Nothing to do. All has beend done at compile time already!
        // The matrix is already filled with a spiral pattern
    
        // Just output
        for (const auto& row : matrix) {
            for (const auto& col : row) std::cout << std::setw(5) << col << ' '; std::cout << '\n';
        }
    }
    

    可以轻松适应不同的坐标系或其他螺旋方向。

    编码愉快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-01
      • 2016-02-14
      • 2022-07-21
      • 1970-01-01
      • 1970-01-01
      • 2018-03-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多