螺旋矩陣 非数组解法
本題要求將給定的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燦杰
#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