趣味编程-螺旋打印
描述
给定一个自然数n,打印1-n之间所有的数,要求:按螺旋形状顺时针打印。以前看到过这道题,说的是从外向内螺旋打印,而前几天又看到一个变种,由内向外打印。比之前的稍微难一点,趁周末闲着没事,总结一下。先上两幅图,大家看一下效果。
由外向内打印
由内向外打印
这两种输出方法,其实大同小异,道理都差不多,会了一种,则另一种不难,先看从外向内打印的
分析
最简单且直观的方法就是k * k的二维数组存储数字,先将数字按照要求填入数组,然后输出整个数组即可
分配数组
对于n个数而言,令k = Ceil(sqrt(n)), 则分配k*k的二维数组即可。比如n = 5时,分配3*3的数组即可。
如何填数
起始点
以左上角为起始点
方向
用一个整数flag来标识方向
flag = 1 - 向右
flag = 2 - 向下
flag = 3 - 向左
flag = 4 - 向上
边界判断
当到达边界的时候应该变换方向,即向右转,如何判断边界?我的方法是,将所有数组元素初始化为-1,则有如下两种情况
1.下标超出二维数组边界,则需转向(注意,转向之前下标需要退回一格)
2.下标未越界,但下一个位置的值不是-1,那么说明它被填充过,也需转向。
填充
重复以下过程直到所有数据填充完毕。
1. 从左至右填充数组,如遇右边界或者下一位置已经被填充过,则改变方向,转入步骤2
2. 从上至下填充数组,如遇下边界或者下一位置已经被填充过,则改变方向,转入步骤3
3. 从右至左填充数组,如遇左边界或者下一位置已经被填充过,则改变方向,转入步骤4
4. 从下至上填充数组,如遇上边界或者下一位置已经被填充过,则改变方向,转入步骤1
整个填充过程如下图
代码
有了以上思路,则写代码不是难事
1 // 从外向内螺旋打印1-n
2 void SpiralPrint1(int n)
3 {
4 // 计算二维数组的维数k
5 int k = ceil(sqrt((float)n)) ;
6
7 // 动态分配二维数组
8 int **a = new int*[k] ;
9 for(int m = 0; m < k; m++)
10 {
11 a[m] = new int[k] ;
12 }
13
14 // 初始化数组元素值为-1
15 for(int x = 0; x < k; ++x)
16 {
17 for(int y = 0; y < k; ++y)
18 {
19 a[x][y] = -1 ;
20 }
21 }
22
23 // 起始数字
24 int num = 1 ;
25
26 // 方向标志,起初向右
27 int flag = 1 ;
28
29 // 起始位置
30 int i = 0;
31 int j = 0 ;
32
33 // 填数
34 while(true)
35 {
36 // 所有数都填完了么?
37 if(num > n)
38 break ;
39
40 // 如果当前位置尚未填充,则填充
41 if(a[i][j] == -1)
42 a[i][j] = num++ ;
43
44 // 寻找下一个可填充位置
45
46 // 从左向右
47 if(flag == 1)
48 {
49 j++ ;
50 if(j == k || a[i][j] != -1) // 到达右边界或已经填充过
51 {
52 j-- ;// 列下标会退
53 flag = 2 ; // 到达边界则向下
54 }
55 }
56
57 // 从上到下
58 else if(flag == 2)
59 {
60 i++ ;
61 if(i == k || a[i][j] != -1)// 到达下边界或已经填充过
62 {
63 i-- ; // 行下标回退
64 flag = 3 ; //转向左
65 }
66 }
67
68 // 从右向左
69 else if(flag == 3)
70 {
71 j-- ;
72 if(j == -1 || a[i][j] != -1)// 到达左边界或已经填充过
73 {
74 j++ ; // 列下标前进
75 flag = 4 ; // 到达边界则向上
76 }
77 }
78
79 // 从下向上
80 else // flag == 4
81 {
82 i-- ;
83 if(i == -1 || a[i][j] != -1)// 到达上边界或已经填充过
84 {
85 i++ ; // 行下标前进
86 flag = 1 ; // 到达边界则向右
87 }
88 }
89 }
90
91 // 输出
92 for(int i = 0; i < k; i++)
93 {
94 for(int j = 0; j < k; j++)
95 {
96 cout << right << setw(3) ;
97
98 if(a[i][j] != -1)
99 cout << a[i][j] ;
100 else
101 cout << " " ;
102 }
103 cout << endl ;
104 }
105 }
由内向外填充
如果上面的搞懂了,则这个也就不难了,不过有一些细节上的东西还需修改一下
起始位置
由于是从内向外填充,所以起始位置不再是(0, 0),需要重新计算,而且还与二维数组的阶数k的奇偶性相关
1 当k为奇数时,起始位置为(k / 2, k / 2),如下:
k = 3,起始位置为(1, 1)
7 8 9
6 1 2
5 4 3
2 当k为偶数时,起始位置为(k / 2 - 1, k / 2 - 1),如下:
k = 4,起始位置为(1, 1)
7 8 9 10
6 1 2 11
5 4 3 12
16 15 14 13
边界判断
与从外向内填充不同的是,边界判断中不会再出现下标越界的情况,只需判断当前位置是否填充过即可
方向转换
从内向外填充时,能转向时优先转向,不能转向时才继续向前填充,而从外向内填充则恰恰相反,无法继续向前填充时才转向。
代码
1 // 从内向外螺旋打印1-n
2 void SpiralPrint(int n)
3 {
4 // 计算二维数组的维数k
5 int k = ceil(sqrt((float)n)) ;
6
7 // 动态分配二维数组
8 int **a = new int*[k] ;
9 for(int m = 0; m < k; m++)
10 {
11 a[m] = new int[k] ;
12 }
13
14 // 初始化数组元素值为-1
15 for(int x = 0; x < k; ++x)
16 {
17 for(int y = 0; y < k; ++y)
18 {
19 a[x][y] = -1 ;
20 }
21 }
22
23 // 起始数字
24 int num = 1 ;
25
26 // 方向标志,起初向上
27 int flag = 4 ;
28
29 // 起始位置,视k的奇偶性而定
30 int i ;
31 int j ;
32
33 if(k & 1)
34 {
35 i = k / 2 ;
36 j = k / 2 ;
37 }
38 else
39 {
40 i = k / 2 - 1 ;
41 j = k / 2 - 1 ;
42 }
43
44 // fill in the array
45 while(true)
46 {
47 // 所有数都填完了么?
48 if(num > n)
49 break ;
50
51 // 如果当前位置尚未填充,则填充
52 if(a[i][j] == -1)
53 a[i][j] = num++ ;
54
55 // 寻找下一个可填充位置
56
57 // 从左向右
58 if(flag == 1)
59 {
60 if (a[i + 1][j] == -1) //下边可填充则转向下
61 {
62 i++ ;
63 flag = 2 ;
64 }
65 else
66 j++ ; //否则继续向右
67 }
68
69 // 从上到下
70 else if(flag == 2)
71 {
72 if(a[i][j - 1] == -1) //左边可填充则转向左
73 {
74 j-- ;
75 flag = 3 ;
76 }
77 else
78 i++ ; //否则继续向下
79 }
80
81 // 从右向左
82 else if(flag == 3)
83 {
84 if(a[i - 1][j] == -1) //上边可填充则转向上
85 {
86 i-- ;
87 flag = 4 ;
88 }
89 else
90 j-- ;
91 }
92
93 // 从下向上
94 else // flag == 4
95 {
96 if (a[i][j + 1] == -1) //右边可填充则转向右
97 {
98 j++ ;
99 flag = 1 ;
100 }
101 else
102 i-- ; //否则继续向上
103 }
104 }
105
106 // 输出
107 for(int i = 0; i < k; i++)
108 {
109 for(int j = 0; j < k; j++)
110 {
111 cout << right << setw(3) ;
112
113 if(a[i][j] != -1)
114 cout << a[i][j] ;
115 else
116 cout << " " ;
117 }
118 cout << endl ;
119 }
120 }