• 螺旋矩陣 非数组解法


    本題要求將給定的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<<"	";
                
            }
            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<<'	';//该圈的基数加上该数在该圈的位置,输出该数
        }
        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

  • 相关阅读:
    .NET框架设计—常被忽视的C#设计技巧
    判断网络是否链接
    ADO.NET入门教程(五) 细说数据库连接池
    爬虫selenium中截图
    爬虫极滑块验证思路
    Linux 磁盘分区、挂载
    linux中crontab任务调度
    第30课 操作符重载的概念
    第29课 类中的函数重载
    第28课 友元的尴尬能力
  • 原文地址:https://www.cnblogs.com/zeedmood/p/5229463.html
Copyright © 2020-2023  润新知