螺旋矩陣 非数组解法

本題要求將給定的n*n個正整數按遞增的順序,填入“螺旋矩陣”。所謂“螺旋矩陣”,是指從左上角第1個格子開始,按順時針螺旋方向填充數字,數字從1開始直到n*n。要求矩陣的規模為n行n列。【不允許使用數組】

輸入格式:

輸入在第1行中給出一個正整數n。

輸出格式:

輸出螺旋矩陣。每行n個數字,共n行。相鄰數字以空格分隔。

輸入樣例:
4
輸出樣例:
1   2   3   4
12 13 14  5
11 16 15  6
10 9   8   7 

============================ 

解法一 By 子木

解決步驟:

1. 矩陣劃分

  1.1 整體劃分

  1.2. 遞歸劃分

2. 解決外圍部分

3. 解決裡邊部分

 

 

1. 矩陣劃分

  這一節希望將矩陣劃分出兩個部分,分別是容易解決的外圍部分,和可以遞歸解決的裡邊部分。

  不能劃分、但可以簡單解決的n<=2的矩陣,將在第3步提及。

  如果n>2,那麼可以將螺旋矩陣分成兩個部分,分別是最外面一圈“空心正方形框”和裡邊一坨“實心正方形” 。假定,只有一個數字的也算邊長為1的正方形。

  示例如下:

  1  1  1               1  1  1  1          1  1  1  1  1
  1  2  1               1  2  2  1          1  2  2  2  1
  1  1  1                  1  2  2  1          1  2  2  2  1        
Fig.1 n=3, 分成兩個部分         1  1  1  1             1  2  2  2  1          
                Fig.2 n=4, 分成兩個部分       1  1  1  1  1 

                             Fig.3 n=5, 分成兩個部分

  這裡有疑問:裡邊那個一定是正方形嗎?為什麼n需要大於2?

      裡邊的實心正方形部分,是在原有的正方形基礎上取出外圍,實際上是將排和列都減去2。

      由於原本是正方形,所以排與列都減去2后,只要邊長(n)大於0,一定仍然為正方形。 

      因此,邊長(n)大於2是為了讓原本正方形能分成兩個部分,並且保證了裡邊的部分仍為正方形。

  下面我們介紹怎樣將一個矩陣分為外圍部分和裡邊部分。

 

   1.1 整體劃分

  當我們想要打印第i行第j列的數字時,怎麼知道這個數字是屬於外圍部分(空心正方形框)還是裡邊部分(實心正方形)呢?

  先定義一個操作“鏡像反轉”:

    鏡像反轉,指的是對一個數字序列1, 2, 3, ..., a-1, a,將其變成1, 2, 3, ..., 2, 1

    舉個例子,12 34 變成 12 21;12 3 45 變成 12 3 21;123 456 變成 123 321.

    對每個數字i, 通項公式是t=a-i+1

  接著定義一個操作"12鏡像反轉":

    12鏡像反轉,指的是將鏡像反轉之後的序列中,大於2的數字全部變成2.

    舉個例子,12 34 變成 12 21;12 3 45 變成 12 2 21;123 456 變成 122 221.

    對每個數字i, 通項公式是t=min{a-i+1, 2}

  如果我們將i“12鏡像反轉”成k ,將j“12鏡像反轉”成t。通過觀察,我們發現,取k和t中比較小的一個,如果是1,這個數字屬於外圍部分;如果是2,這個數字屬於裡邊部分。

对i(行):      1  1  1  1      镜像后k:1  1  1  1  
                         2  2  2  2                      2  2  2  2
                         3  3  3  3                      2  2  2  2
                         4  4  4  4                      1  1  1  1
                            
对j(列):      1  2  3  4      镜像后t:1  2  2  1
                         1  2  3  4                     1  2  2  1
                         1  2  3  4                     1  2  2  1
                         1  2  3  4                     1  2  2  1  

k和t中較小一個(記為n):

                         1  1  1  1
                         1  2  2  1
                         1  2  2  1
                         1  1  1  1 

 

 1.2. 遞歸劃分

  如果n>4,我們發覺裡邊一坨“實心正方形”又可以繼續拆成外面一圈“空心正方形框”和裡邊一坨“實心正方形”。

  直到變成步驟2中Fig.1或Fig.2為止。

    1  1  1  1  1    
    1  2  2  2  1    2  2  2            1  1  1
    1  2  2  2  1 =>      2  2  2   =>     1  2  1         
    1  2  2  2  1      2  2  2     1  1  1           
    1  1  1  1  1 

         Fig.4 n=5, 不斷分兩個部分

  實際上這個問題變成解決外圍部分和裡邊部分的問題。而裡邊部分是可以又分成解決其外圍部分和裡邊部分的子問題。

  裡邊部分不斷剝離外圍,最終會變成Fig.1或Fig.2的簡單問題。

  因此我們下面要做的是:1)解決外圍部分;2)【子問題】解決裡邊部分>>>[本質]解決Fig.1或Fig.2的裡邊部分。

 

2. 解決外圍部分

  實際上是一個順時針矩陣,分成上下左右四個部分解決:

上:extra=j-n+1;
下:extra=(2*an-1)+(an- ( j-(n-1) )  );
左:extra=(n-1)+an-i+3*an-2; 
右:extra=an+(i-(n-1)-1);
i,j是排數和列數。an是外圍正方形的邊長;
n是12鏡像反轉的結果,外圍和裡邊的標記;extra是這個外圍位置的數值。

  這裡解決了沒有外圍的外圍部分的情形。有外圍的外圍部分,我們稱之為裡邊,并在下一步解決。

 

3. 解決裡邊部分

  如果將裡邊部分不斷劃分出外圍,我們知道,最後會到達Fig.1或Fig.2的情況。

  也就是說大部分複雜的裡邊部分,說到底就是很多圈外圍來圍著核心,核心是n小於等於2的螺旋矩陣。示例如下:

  1            1  2 
        4  3

    可以看到,這兩個小螺旋矩陣,也還是一個簡單的順時針矩陣,又可以看做外圍來解。

  外圍的數值我們在第2步已經可以計算了。所謂裡邊部分,實際上就是一堆外圍。

  這裡用遞歸函數around來計算這個位置到底有多少外圍數字,再加上它自身這個的外圍應顯示的數(稱為額外數),得到這個位置的最終數值unit。

  unit=around(n-1,a,all)+extra;//数值为外圍數字個數加额外数

 

 

 代碼如下

#include <iostream>
using namespace std;
int around(int, int, int);
int main()
{
    int n,m,an,a,all,i,j,t,k,center,unit,extra;//an为所在正方形边长,all为正方形总个数
    cout<<"Enter the size:";
    cin>>m;
    a=m ;//求出a为最大正方形(n=1)边长
    if(a%2==0)
        all=a/2;//奇数和偶数会使正方形个数发生变化
    else
        all=a/2+1;
    for(i=1;i<=a;i++)
    { 
        if (i<=a/2)
            t=i;//若i超过中间线,使t为i镜像对称
        else
            t=a-i+1;
        for(j=1;j<=a;j++)
        {
            if (j<=a/2)
                k=j;//同理,使k为j镜像对称
            else
                k=a-j+1;      
            if(k<=t)
                n=k;//k和j间较小一个决定了所在正方形n
            else
                n=t;
            if(a%2==0) //奇偶数不一样
            {
                an=2*(all-n+1);//第n个正方形边长
                center=a/2;//求中间线
            }
            else
            {  
                an=2*(all-n+1)-1;
                center=a/2+1;
            }      
            if(i==n) //在顶边
                extra=j-n+1;
            else if(i<(n-1)+an) //在两边
            {
                if(j<=center) //在左边
                    extra=(n-1)+an-i+3*an-2;
                else          //在右边
                    extra=an+(i-(n-1)-1);
                
            }
            else   //在底边
                extra=(2*an-1)+(an- ( j-(n-1) )  );
            unit=around(n-1,a,all)+extra;//数值为外圍數字個數加额外数
            cout<<unit<<"\t";
            
        }
        cout<<endl;
    }
    return 0;
}

int around(int n,int a,int all)//递归不斷分解出外围數字個數
{
    int an;
    if(a%2==0)
        an=2*(all-n+1);  //求边长
    else
        an=2*(all-n+1)-1;
    if(n==0)
        return 0;
    else
        return around(n-1,a,all)+4*an-4;//由边长求周长
}

 

=====================================

解法2 by燦杰

想法:
用第i行,第j列,i,j作为变量,用一道公式算出坐标为(i,j)的数。
 
做法:
先将矩阵通过对称折叠成左上角的四分之一大小矩阵,
在同一个正方形圈的数与在四分之一矩阵内的圈内的数坐标重叠,折叠后的数的坐标用(ii,jj)表示;
可找到,ii,jj中小的那个数就是该数所在的圈,然后可由n和圈数算出小于该圈所有数的最大的整数,这里我称之为基数;
然后将该数所在圈移动至左上角,方便计算该数在圈内的位置,得到新坐标(i1,j1)
由新坐标的i1,j1参与判断,选用公式计算该数在圈内所在位置,其实形象上看是一个将圈拉成直线的过程。
然后基数+位数,得出该数大小。
 
#include<iostream>
using namespace std;
int main()
{
  int n,i,j,ii,jj,k,i1,j1;
  cout<<"请输入n:"<<endl;
  cin>>n;
  for(i=1;i<=n;i++)//i表示第i行,j表示第j列,逐行逐个数字输出
  {
    for(j=1;j<=n;j++)
    {
      if(i>n/2) //将矩阵折叠成四分之一
        ii=n-i+1;//ii,jj分别表示在四分之一矩阵中的第几行,第几列
      else
        ii=i;
      if(j>n/2)
        jj=n-j+1;
      else
        jj=j;
      if(ii>jj)//求出从外内,该数在第几个圈,并在此用ii表示第几圈
        ii=jj;
        i1=i-ii+1;//将该数所在的圈移动至左上角,然后用i1,j1表示该数的新坐标
        j1=j-ii+1;
      if(i>j)//将圈拉直,计算该数为该圈的第几个数
        k=4*n-8*ii+7-i1-j1;
      else
        k=i1+j1-1;
        cout<<n*n-(n-2*ii+2)*(n-2*ii+2)+k<<'\t';//该圈的基数加上该数在该圈的位置,输出该数
    }
    cout<<endl;//换行
  }
}

 

=====================================

解法3 by朱鵬

分為左右兩個部分來解。

#include <iostream>
using namespace std;
void show(int,int,int,int,int);
int main()
{
 int n,i,a,j=0;
 int b=0;
 cout<<"输入你想构建的N:"<<endl;
 cin>>n;
 for(i=1;i<=n;i++)
 {
  if(i<=n/2)
  {
   show(i,i,n,b,j);
  }
  else
  {
   a=n-i+1;
   show(i,a,n,b,j);
  }
   cout<<endl;
 }
}
void show(int k,int g,int n,int b,int j)
{
 int i,d=n;
 if(j>0)
 {
   k--;g--;
  
  b+=4*n-4;
  n=n-2;
 }
 if(g>1)
 {
  cout<<"   "<<b+(4*n-4-k+2);
  j++;
  show(k,g,n,b,j);
  cout<<"   "<<b+n+k-1;
 }
 else 
 {
  if(k!=n)
  {
   for(i=b+1;i<=b+n;i++)
    cout<<"   "<<i;
  }
  else
  {
   int c=b+(4*n-4-n+2),l=b+2*n-1;
   for(i=c;i>=l;i--)
    cout<<"   "<<i;
  }
 }

 

=====================================

解法四 by禮權

禮權的代碼沒有發出來……

如果沒記錯的話,大概是把正方形分成3部分來解

1  1  1  1  1

2  1  1  1  3

2  2  1  3  3

2  1  1  1  3

1  1  1  1  1

 

=====================================

TA课上提及,对自己以前的博文整理,提高可读性。原文鏈接:

http://user.qzone.qq.com/312677150/blog/1289724415

posted @ 2016-03-06 10:56  MooreZHENG  阅读(372)  评论(0编辑  收藏  举报