【笔面大全】螺旋矩阵问题汇总

一、单螺旋矩阵

1、从外向内螺旋

问题

按顺时针方向构建一个m * n的螺旋矩阵(或按顺时针方向螺旋访问一个m * n的矩阵):

在不构造螺旋矩阵的情况下,给定坐标ij值求其对应的值f(i, j)

比如对11 * 7矩阵, f(6, 0) = 27  f(6, 1) = 52 f(6, 3) = 76  f(6, 4) = 63

 【笔面大全】螺旋矩阵问题汇总_第1张图片

构建螺旋矩阵

对m * n 矩阵,最先访问最外层的m * n的矩形上的元素,接着再访问里面一层的 (m - 2) * (n - 2) 矩形上的元素…… 最后可能会剩下一些元素,组成一个点或一条线(见图1)。

对第i个矩形(i=0, 1, 2 …),4个顶点的坐标为:

(i, i) ----------------------------------------- (i, n–1-i)

|                                                    |

|                                                    |

|                                                    |

(m-1-i, i) ----------------------------------------- (m-1-i, n-1-i) 

要访问该矩形上的所有元素,只须用4个for循环,每个循环访问一个点和一边条边上的元素即可(见图1)。另外,要注意对最终可能剩下的1 * k 或 k * 1矩阵再做个特殊处理。

 

代码:

inline void act(int t) { printf("%3d ", t); }

const int small = col < row ? col : row;
const int count = small / 2;

for (int i = 0; i < count; ++i) {
    const int C = col - 1 - i;
    const int R = row - 1 - i;
    for (int j = i; j < C; ++j) act(arr[i][j]);
    for (int j = i; j < R; ++j) act(arr[j][C]);
    for (int j = C; j > i; --j) act(arr[R][j]);
    for (int j = R; j > i; --j) act(arr[j][i]);
}

if (small & 1) {
    const int i = count;
    if (row <= col) for (int j = i; j < col - i; ++j) act(arr[i][j]);
    else for (int j = i; j < row - i; ++j) act(arr[j][i]);
}

如果只是构建螺旋矩阵的话,稍微修改可以实现4个for循环独立:

const int small = col < row ? col : row;
const int count = small / 2;

for (int i = 0; i < count; ++i) {
    const int C = col - 1 - i;
    const int R = row - 1 - i;
    const int cc = C - i;
    const int rr = R - i;
    const int s = 2 * i * (row + col - 2 * i) + 1;

    for (int j = i, k = s; j < C; ++j) arr[i][j] = k++;
    for (int j = i, k = s + cc; j < R; ++j) arr[j][C] = k++;
    for (int j = C, k = s + cc + rr; j > i; --j) arr[R][j] = k++;
    for (int j = R, k = s + cc * 2 + rr; j > i; --j) arr[j][i] = k++;
}

if (small & 1) {
    const int i = count;
    int k = 2 * i * (row + col - 2 * i) + 1;
    if (row <= col) for (int j = i; j < col - i; ++j) arr[i][j] = k++;
    else for (int j = i; j < row - i; ++j) arr[j][i] = k++;
}

关于s的初始值取 2 * i * (row + col - 2 * i) + 1请参考下一节。


由于C++的二维数组是通过一维数组实现的。二维数组的实现一般有下面三种:

静态分配足够大的数组;

动态分配一个长为m*n的一维数组;

动态分配m个长为n的一维数组,并将它们的指针存在一个长为m的一维数组。

二维数组的不同实现方法,对函数接口有很大影响。


给定坐标直接求值f(x, y)

如前面所述,对第i个矩形(i=0, 1, 2 …),4个顶点的坐标为:

(i, i) ----------------------------------------- (i, n–1-i)

|                                                    |

|                                                    |

|                                                    |

(m-1-i, i) ----------------------------------------- (m-1-i, n-1-i) 

对给定的坐标(x,y),如果它落在某个这类矩形上,显然其所在的矩形编号为:

k = min{x, y, m-1-x, n-1-y}

m*n矩阵删除访问第k个矩形前所访问的所有元素后,可得到(m-2*k)*(n-2*k)矩阵,因此已访问的元素个数为:m*n-(m-2*k)*(n-2*k)=2*k*(m+n-2*k),因而 (k,k)对应的值为:

T(k) = 2*k*(m+n-2*k)+ 1

对某个矩形,设点(x, y)到起始点(k,k)的距离d = x-k + y-k = x+y-2*k

① 向右和向下都只是横坐标或纵坐标增加1,这两条边上的点满足f(x, y) = T(k) + d

② 向左和向下都只是横坐标或纵坐标减少1,这两条边上的点满足f(x, y) = T(k+1) - d

如果给定坐标的点(x, y),不在任何矩形上,则它在一条线上,仍满足f(x, y) = T(k) + d

int getv(int row, int col, int max_row, int max_col) // row < max_row, col < max_col
{
    int level = min(min(row, max_row - 1 - row), min(col, max_col - 1 - col));
    int distance = row + col - level * 2;
    int start_value = 2 * level * (max_row + max_col - 2 * level) + 1;

    if (row == level || col == max_col - 1 - level || (max_col < max_row && level * 2 + 1 == max_col))
        return start_value + distance;

    int next_value = start_value + (max_row + max_col - 4 * level - 2) * 2;

    return next_value - distance;
}

特别说明

上面的讨论都是基于m*n矩阵的,对于特例n*n矩阵,可以做更多的优化。比如构建螺旋矩阵,如果n为奇数,则矩阵可以拆分为几个矩形加上一个点。前面的条件判断可以优化为:

if (small & 1) act[count][count];

甚至可以调整4个for循环的遍历元素个数(前面代码,每个for循环遍历n-1-2*i个元素,可以调整为:n-2*i,n-1-2*i, n-1-2*i,n-2-2*i)从而达到省略if判断。

 

测试代码

代码1

//螺旋矩阵,给定坐标直接求值 by flyinghearts
//www.cnblogs.com/flyinghearts

#include<iostream>
#include<algorithm>

using std::min;
using std::cout;

/*
int getv2(int row, int col, int max_row, int max_col) // row < max_row, col < max_col
{
    int level = min(min(row, max_row - 1 - row), min(col, max_col - 1 - col));
    int distance = row + col - level * 2;
    int start_value = 2 * level * (max_row + max_col - 2 * level) + 1;

    if (row == level || col == max_col - 1 - level) return start_value + distance;

    //++level; int next_value = 2 * level * (max_row + max_col - 2 * level) + 1;

    int next_value = start_value + (max_row + max_col - 4 * level - 2) * 2;
    if (next_value > max_col * max_row) return start_value + distance;

    return next_value - distance;
}
*/

int getv(int row, int col, int max_row, int max_col) // row < max_row, col < max_col
{
    int level = min(min(row, max_row - 1 - row), min(col, max_col - 1 - col));
    int distance = row + col - level * 2;
    int start_value = 2 * level * (max_row + max_col - 2 * level) + 1;

    if (row == level || col == max_col - 1 - level || (max_col < max_row && level * 2 + 1 == max_col))
       return start_value + distance;

    //++level; int next_value = 2 * level * (max_row + max_col - 2 * level) + 1;

    int next_value = start_value + (max_row + max_col - 4 * level - 2) * 2;

    return next_value - distance;
}

int main()
{
    int test[][2] = {{5, 5}, {5, 7}, {7, 5}, {4, 4}, {4, 6}, {6, 4}};
    const int sz = sizeof(test) / sizeof(test[0]);
    for (int k = 0; k < sz; ++k) {
       int M = test[k][0];
       int N = test[k][1];  
       for (int i = 0; i < M; ++i) {
          for (int j = 0; j < N; ++j)
            cout.width(4), cout << getv(i, j, M, N) << " ";
          cout << "\n"; 
       }
       cout << "\n";
    }
}

代码2:

//螺旋矩阵 by flyinghearts#qq.com
//www.cnblogs.com/flyinghearts

#include<iostream>

int counter = 0;

inline void act(int& t)
{
   //std::cout.width(3), std::cout << t;
   t = ++::counter;
}

void act_arr(int *arr, int row, int col, int max_col) //col < max_col
{
    const int small = col < row ? col : row;
    const int count = small / 2;

    int *p = arr;

    for (int i = 0; i < count; ++i) {
       const int C = col - 1 - 2 * i;
       const int R = row - 1 - 2 * i;

       for (int j = 0; j < C; ++j) act(*p++);
       for (int j = 0; j < R; ++j) act(*p), p += max_col;
       for (int j = 0; j < C; ++j) act(*p--);
       for (int j = 0; j < R; ++j) act(*p), p -= max_col;

       p += max_col + 1;
    }

    if (small & 1) {
       const int i = count;
       if (row <= col) for (int j = 0; j < col - 2 * i; ++j) act(*p++);
       else for (int j = 0; j < row - 2 * i; ++j) act(*p), p += max_col;
    }
}

void act_arr(int* arr[], int row, int col)
{
    const int small = col < row ? col : row;
    const int count = small / 2;

    for (int i = 0; i < count; ++i) {
       const int C = col - 1 - i;
       const int R = row - 1 - i;

       for (int j = i; j < C; ++j) act(arr[i][j]);
       for (int j = i; j < R; ++j) act(arr[j][C]);
       for (int j = C; j > i; --j) act(arr[R][j]);
       for (int j = R; j > i; --j) act(arr[j][i]);
    }

    if (small & 1) {
       const int i = count;
       if (row <= col) for (int j = i; j < col - i; ++j) act(arr[i][j]);
       else for (int j = i; j < row - i; ++j) act(arr[j][i]);
    }
}

void act_arr_2(int* arr[], int row, int col)
{
    const int small = col < row ? col : row;
    const int count = small / 2;
    for (int i = 0; i < count; ++i) {
       const int C = col - 1 - i;
       const int R = row - 1 - i;
       const int cc = C - i;
       const int rr = R - i;
       const int s = 2 * i * (row + col - 2 * i) + 1;

       for (int j = i, k = s; j < C; ++j) arr[i][j] = k++;
       for (int j = i, k = s + cc; j < R; ++j) arr[j][C] = k++;
       for (int j = C, k = s + cc + rr; j > i; --j) arr[R][j] = k++;
       for (int j = R, k = s + cc * 2 + rr; j > i; --j) arr[j][i] = k++;
    }

    if (small & 1) {
       const int i = count;
       int k = 2 * i * (row + col - 2 * i) + 1;
       if (row <= col) for (int j = i; j < col - i; ++j) arr[i][j] = k++;
       else for (int j = i; j < row - i; ++j) arr[j][i] = k++;
    }
}

void print_arr(int *arr, int row, int col, int max_col) //col < max_col
{
    for (int i = 0, *q = arr; i < row; ++i, q += max_col) {
       for (int *p = q; p < q + col; ++p)
          std::cout.width(4), std::cout << *p;
       std::cout << "\n";
    }
    std::cout << "\n";
}

void print_arr(int* a[], int row, int col) //col < max_col
{
    for (int i = 0; i < row; ++i) {
       for (int j = 0; j < col; ++j)
           std::cout.width(4), std::cout << a[i][j];
       std::cout << "\n"; 
    }  
    std::cout << "\n";
}

void test_1()
{
    const int M = 25;
    const int N = 25;

    int a[M][N];
    int test[][2] = {{5, 5}, {5, 7}, {7, 5}, {4, 4}, {4, 6}, {6, 4}};

    const int sz = sizeof(test) / sizeof(test[0]);
    std::cout << "Test 1:\n";

    for (int i = 0; i < sz; ++i) {
       int row = test[i][0];
       int col = test[i][1];
       if (row < 0 || row > M) row = 3;
       if (col < 0 || col > N) col = 3;
       ::counter = 0;

       act_arr(&a[0][0], row, col, N);
       print_arr(&a[0][0], row, col, N);
    }
}

void test_2()
{
    int test[][2] = {{5, 5}, {5, 7}, {7, 5}, {4, 4}, {4, 6}, {6, 4}};
    const int sz = sizeof(test) / sizeof(test[0]);
    std::cout << "Test 2:\n";

    for (int i = 0; i < sz; ++i) {
       int row = test[i][0]; 
       int col = test[i][1]; 
       int **arr = new int*[row];

       for (int i = 0; i < row; ++i) arr[i] = new int[col];

       ::counter = 0;
       act_arr(arr, row, col);
       print_arr(arr, row, col);

       for (int i = 0; i < row; ++i) delete[] arr[i];

       delete[] arr; 
    }
}

int main()
{
    test_1();
    test_2();
}

2、从内向外螺旋

           21   22................
           20   7   8   9   10
           19   6   1   2   11
           18   5   4   3   12
           17  16  15  14  13
问题有两个:
1. 编程实现输出这个矩阵
2. 设1点的坐标是(0,0),x方向向右为正,y方向向下为正.例如:7的坐标为(-1,-1) ,2的坐标为(0,1),3的坐标为(1,1).编程实现输入任意一点坐标(x,y),输出所对应的数字。

思路1:

详见:http://www.cnblogs.com/drizzlecrj/archive/2007/04/10/706784.html
1. 第一个问题我是采用模拟进行构造的,可以看到从1开始的方向变化始终是 right->down->left->up,
所持续走的长度为1->1->2->2->3->3->...,发现了这个规律不难写出代码了!注意下面我把1的位置设置
在((n-1)/2, (n-1)/2)的位置。

void Simulate(int n)
{
    int x, y;
    x = y = (n - 1) / 2; //1的位置
    data[x][y] = 1;
    int len = 1;
    int count = 0;
    int num = 2;
    DIRECTION dir = RIGHT;
    while(num  <= n * n)
    {
        for(int i = 0; i < len; i++)
        {
            switch(dir)
            {
            case LEFT:
                --y;    break;
            case RIGHT:
                ++y;     break;
            case UP:
                --x;    break;
            case DOWN:
                ++x;    break;
            default:    break;
            }
            data[x][y] = num++;
        }
        count++;
        if(count == 2)
        {
            count = 0;
            len++;    
        }
        dir = (DIRECTION)((dir + 1) % 4);
    }
}

2. 第二个问题我也是先找出规律,然后进行模拟。
首先,不难看出n*n的螺旋矩阵的右下角的坐标一定是(m, m),这里m=n-1
通过观察,可以看出 n=1的时候,右下角(0,0)的值为1,当n=2的时候,右下角(1,1)的坐标值为(3,3),当n=3的时候,右下角(2,2)的坐标值为13.直觉告诉我,这个值是关于n的二次函数,设f(n) = a*n^2 + b*n + c
联立方程组,可以求得a,b,c。 最终算出来的f(n) = 4*n^2 - 2*n + 1
下面再根据(x,y)和右下角(n-1,n-1)之间的关系,计算出值即可。这里要注意当x的值与n-1相同时,应优先考虑y与-m是否有联系。这就要求在函数中要注意x,y的判断先后顺序了。
代码如下:

//以(1,1)所在位置作为原点,向右作为x正半轴,向下作为y正半轴
int GetValue(int x, int y)
{
    int m = max(abs(x), abs(y));
    int rightBottom = m * m * 4 - 2 * m + 1;
    int value = 0;
    if(x == -m)
    {
        value = rightBottom + 2 * m + m - y;
    }
    else if( y == m)
    {
        value = rightBottom + m - x;
    }
    else if(y == -m)
    {
        value = rightBottom + 4 * m + x + m;
    }
    else if( x == m )
    {
        value = rightBottom - (m - y);
    }
    return value;
}

我的实现:

const int n = 10;
int a[n][n];

int main() {
    int x = (n - 1) / 2;
    int y = x;
    int num = 2;
    int sum = n * n;
    a[x][y] = 1;

    int i = 0;
    int step = 1;
    while (num <= sum) {
        for (int j = 0 ;num <=sum && j <step ;++j) {
            switch (i) {
                case 0:
                    ++y;
                    break;
                case 1:
                    ++x;
                    break;
                case 2:
                    --y;
                    break;
                case 3:
                    --x;
                    break;
            }
            a[x][y] = num ++;
        }
        if (i % 2 ==1)
            ++step;
        i = (i + 1) %4;
    }
    for (int i = 0; i< n; ++i) {
        for (int j = 0; j < n; ++j)
            printf("%8d",a[i][j]);
        cout<<endl;
    }
}


int GetValue(int n, int i, int j)//这也是个不错的思路
{
        assert(n>0 && i<n && j<n);

        int x = n/2;
        int y = n/2;

        if (n%2 == 0)
                x--;
        if (x == i && y == j) return 1;

        bool bXDec = false;
        bool bYDec = true;
        int nSteps = 1;
        int nRet = 1;

        while (nSteps <= n && nRet <= n*n)
        {
                //calculate each step
                int nLeftX = nSteps;
                int nLeftY = nSteps;

                while (0 != nLeftY)
                {
                        bYDec ? y-- : y++;
                        nRet++;
                        if (i == x && j == y) return nRet;
                        nLeftY--;
                }
                while (0 != nLeftX)
                {
                        bXDec ? x-- : x++;
                        nRet++;
                        if (i == x && j == y) return nRet;
                        nLeftX--;
                }
                nSteps++;
                bXDec = !bXDec;
                bYDec = !bYDec;
        }
        return -1;
}

思路2:

详见http://www.cnblogs.com/eshizhan/archive/2010/06/01/1749013.html

 【笔面大全】螺旋矩阵问题汇总_第2张图片

看出规律没?找张草稿纸研究研究。

我当时是这么想的:

【笔面大全】螺旋矩阵问题汇总_第3张图片

红线方程y=x,绿线方程y=-x+4,4为矩形边长。

两条直线将区域分为四个部分,划分好每个区域的边界值,每个区域的坐标变化规律有四种,x++,y++,x--,y--,接下来仔细分析就能得到算法:

?
void SpiralArray(int size,int** ar)
{   
    int a=size/2*2+1;//保证边长为奇数
    int y=a/2,x=a/2;//从中心点开始
    for (int i=1;i<=size*size;i++)//(int
 i=size*size;i>=1;i--)
    {
        if (x<=a-y-1&&x>=y)
        {
            ar[y][x]=i;
            x++;
        }
        else if (x>a-y-1&&x>y)
        {
            ar[y][x]=i;
            y++;
        }      
        else if (x>a-y-1&&x<=y)
        {
            ar[y][x]=i;
            x--;
        }
        else if (x<=a-y-1&&x<y)
        {
            ar[y][x]=i;
            y--;
        }
    }
}


二、双螺旋矩阵

详见:http://blog.csdn.net/dennis101/article/details/3053739

双螺旋矩阵的定义如下,矩阵的最中心是1,往上是2,右拐3,向下4,然后依次5、6,7...构成一条顺序增大的螺旋线,此外,如果从中心往下走的话,也是一条对称的螺旋线。题目是给定一个矩阵维度N,将其打印出来,示例如下。要求在纸上把代码写完整,时间半小时左右。

  25    14    15    16    17    18    19
  24    13     6     7       8     9      20
  23    12     5     2       3    10     21
  22    11     4     1       4    11     22
  21    10     3     2       5    12     23
  20     9      8     7       6    13     24
  19    18    17    16    15    14    25

前面有一篇螺旋矩阵的日志,那里面用的方法都是分析坐标,用公式去计算当前位置的值。现在发现其实模拟可以更简单,只要从(0,0)开始,一圈一圈往里走就行了,再根据对称,把另一半也算出来。代码如下

#include <iostream>
using namespace std;
int dx[4] = {1,0,-1,0};
int dy[4] = {0,1,0,-1};
int M[101][101];
int main()
{
   int n;
   cout << "input an odd integer: ";
   cin >> n;
   int start = (n * n + 1) / 2;
   int x = 0, y = 0, side = n - 1, dir = 0, cnt = 0;
   int temp = side;
   while(start > 0)
   {
      M[x][y] = M[n-1-x][n-1-y] = start;
      x += dx[dir];
      y += dy[dir];
      cnt++;
      start--;
  
      if(cnt == side)
      {
         if(dir % 2 == 0)
         {
            if(side == temp)
               side--;
            else
               side -= 2 ;
         }
         dir = (dir + 1) % 4;
         cnt = 0;
      }
   }
   for(int i = 0; i < n; i++)
   {
      for(int j = 0; j < n; j++)
          cout << M[i][j] << "\t";
      cout << endl;
   }
}

你可能感兴趣的:(算法,螺旋矩阵)