数字旋转矩阵
“螺旋数字方阵”解题报告
输入有多组数据,每组只有一行,包含x,y(1 <= x,y <= 30)和t,输出x*y螺旋方阵,如果t=0,就输出逆时针螺旋方阵,否则输出顺时针的
样例输入:
3 5 0
4 4 1
样例输出(每个数字要占四个格子,输出完一组就接着输出一个空行):
1 12 11
2 13 10
3 14 9
4 15 8
5 6 7
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7
通过记录:
方法一:
Name: "goal00001111" Problem ID "25"
Submit Time: 2010/4/28-14:59
G++: Compile OK
Test 1: Accepted Time = 1344 ms
--------------------------------
Problem ID 25
Test Result Accepted
Total Time 1344 ms
Total Memory 172 Kb / 10000 Kb
Code Length 1797 Bytes
方法二:
Name: "goal00001111" Problem ID "25"
Submit Time: 2010/4/28-14:51
G++: Compile OK
Test 1: Accepted Time = 1400 ms
--------------------------------
Problem ID 25
Test Result Accepted
Total Time 1400 ms
Total Memory 164 Kb / 10000 Kb
Code Length 1206 Bytes
方法三:
Name: "goal00001111" Problem ID "25"
Submit Time: 2010/4/29-07:51
G++: Compile OK
Test 1: Accepted Time = 1368 ms
--------------------------------
Problem ID 25
Test Result Accepted
Total Time 1368 ms
Total Memory 164 Kb / 10000 Kb
Code Length 1203 Bytes
题目分析:
螺旋数字矩阵与蛇形矩阵一样,是练习巩固二维数组知识的经典题目。
基本思路是模拟生成自然数序列的过程,依次给二维数组的各个元素赋值,最后输出数组。
由于题目要求的输出可能有逆时针和顺时针两种方向,因此在模拟的时候要分两种情况。解决此问题有两种思路:一是分别为逆时针和顺时针编写代码;二是只编写一段代码,但多设置一个指示方向的变量dir,根据dir的值来决定旋转方向——此思路来自网友wind496,特向其表示感谢——其原理在于通过使行下标不变,列下标增1或减1,达到输入横向数据的目的;同理可以使列下标不变,行下标增1或减1,以输入纵向数据。
问题的另一个难点认清边界,避免出界和覆盖已写数据。我也采用了两种方法:一是设置四个边界变量:up,down,left,right。在程序执行过程中,确保(up <= down && left <= right)。若为逆时针旋转,则按照顺序排列纵排数字,顺序排列横排数字,逆序排列竖排数字,逆序排列横排数字的顺序生成数据。不断改变四个边界变量的值,直到跳出循环。
另一种方法是多分配两行,列,设置“围墙”,以避免出界,判断map[i][j]是否等于0,以避免覆盖已写数据,当(row * col > s)时跳出循环。
两种算法比较:在处理旋转问题时,算法一思路直接,易于理解,但代码较长;算法二思路巧妙,但有些晦涩难懂——如果你能一次性领会其思路的话,说明你的理解能力高于常人——至少比我强,因为我最初阅读网友wind496的代码时,想了老半天才想通,而且,我已经对其代码进行了修改,更简洁,也更晦涩了,呵呵。
在处理边界问题时,算法一就不如算法二了,它是通过某点位置是否在边界范围内来决定是否输入数据;而算法二是通过判断该点是否为已处理点((0 == map[i] [j])表示该点未处理,否则已处理过)来决定是否输入数据。算法一有可能出现覆盖原有数据的错误(我是多次DEBUG后才解决该问题的),而算法二则不存在上述问题,而且通过设置“围墙”,巧妙地解决了越界的问题。
鉴于算法二给人们造成理解上的困难,我对其进行了改进,不是设置方向变量,而是设置二维方向数组dir[8][2] = {{0,1},{1,0},{0,-1},{-1,0},{-1,0},{0,-1},{1,0},{0,1}},dir[i][0]和dir[i][1]分别表示行下标和列下标的增减情况。其中i=[0..3]表示了顺时针旋转的情形,{0,1}表示行下标不变,列下标增1,实现顺序记录横向数据;{1,0}表示列下标不变,行下标增1,实现顺序记录纵向数据;{0,-1}表示行下标不变,列下标减1,实现逆序记录横向数据;{-1,0}表示列下标不变,行下标减1,实现逆序记录横向数据。多次重复上述过程,就可实现顺时针记录螺旋数字矩阵了。同理,i=[4..7]表示了逆时针旋转情形。
算法二的改进版增加了一个二维方向数组,使得代码量和理解难度都有所降低,也算是物有所值吧!
说明:
算法思想:模拟过程, 设置方向指针。
数据结构:二维数组。
时间复杂度:O(row * col);
空间复杂度:三种方法均为O(MAX*MAX)。
程序语言:c++。
附注:关于“螺旋数字方阵”的更多解法请参考《飞燕之家在线测评论坛OnlineJudge ? 新手习题区(OnlineJudge) ? 习题 25:螺旋数字方阵★:
http://yzfy.org/dis/listpost.php?tid=46&extra=page%3D1&page=1
c++代码:
#include <iostream>
#include <iomanip>
using namespace std;
const int MAX = 30;
void GetMatrix(int map[][MAX], int x, int y, int t);
int main()
{
int matrix[MAX][MAX] = {0};
int x, y, t;
while (cin >> x >> y >> t)
{
GetMatrix(matrix, y, x, t);
}
return 0;
}
/*
函数名称:GetMatrix
函数功能:输出row*col螺旋矩阵
输入变量:int map[][MAX]:存储了螺旋矩阵的二维数组
int row, col: 螺旋矩阵的行数和列数
int t:决定输出螺旋矩阵的方向,如果t=0,逆时针输出,否则顺时针输出
输出变量:int map[][MAX]:存储了螺旋矩阵的二维数组
返回值:无
*/
方法一:分别为逆时针和顺时针编写代码。
void GetMatrix(int map[][MAX], int row, int col, int t)
{
int up =0, down = row - 1, left = 0, right = col - 1;//设定四个边界
int num = 0;
if (0 == t)
{
while (up <= down && left <= right)
{
for (int i=up; i<=down; i++)//顺序排列纵排数字
map[i][left] = ++num;
if (left == right) //避免重复输出列
break;
for (int i=++left; i<=right; i++)//顺序排列横排数字
map[down][i] = ++num;
if (up == down) //避免重复输出行
break;
for (int i=--down; i>=up; i--)//反序排列竖排数字
map[i][right] = ++num;
for (int i=--right; i>=left; i--)//反序排列横排数字
map[up][i] = ++num;
up++;
}
}
else
{
while (up <= down && left <= right)
{
for (int i=left; i<=right; i++)//顺序排列横排数字
map[up][i] = ++num;
if (up == down) //避免重复输出行
break;
for (int i=++up; i<=down; i++)//顺序排列竖排数字
map[i][right] = ++num;
if (left == right) //避免重复输出列
break;
for (int i=--right; i>=left; i--)//反序排列横排数字
map[down][i] = ++num;
for (int i=--down; i>=up; i--)//反序排列竖排数字
map[i][left] = ++num;
left++;
}
}
for (int i=0; i<row; i++) //输出矩阵
{
for (int j=0; j<col; j++)
cout << setw(4) << map[i][j];
cout << endl;
}
cout << endl;
}
方法二:设置一个指示方向的变量dir,根据dir的值来决定旋转方向。注意要多分配两行,列,设置“围墙”,以避免出界。
void GetMatrix(int map[][MAX], int row, int col, int t)
{ //数据初始化
for (int i=0; i<=row+1; i++)
for (int j=0; j<=col+1; j++)
{
if (0 == i || 0 == j || row < i || col < j)
map[i][j] = 1;
else
map[i][j] = 0;
}
int dir = (0 == t) ? -1 : 1; //指示方向:1表示顺时针,-1表示针逆时针
int addrow = 0, addcol = dir; //分别表示行下标和列下标的前进方向:前进,后退或不动
int s, i, j;
s = i = j = map[1][1] = 1;
while (row * col > s)
{
while (0 == map[i+addrow][j+addcol]) //可输入数据
map[i+=addrow][j+=addcol] = ++s; //改变行,列下标的值,同时记录该点数据
//此处考验你的阅读理解能力,呵呵!
if (0 == addrow) //若刚刚输入的是横向数据,转为纵向输入
{
addrow = (1 == addcol) ? dir : -dir;//若为横向递增,则顺着旋转方向走(addrow=dir);否则逆着走
addcol = 0;
}
else //若刚刚输入的是纵向数据,转为横向输入
{
addcol = (1 == addrow) ? -dir : dir;//若为纵向递增,则逆着旋转方向走(addrow=-dir);否则顺着走
addrow = 0;
}
}
for (int i=1; i<=row; i++) //输出矩阵
{
for (int j=1; j<=col; j++)
cout << setw(4) << map[i][j];
cout << endl;
}
cout << endl;
}
方法三:算法二的改进版,不是设置方向变量,而是设置二维方向数组。
void GetMatrix(int map[][MAX], int row, int col, int t)
{ //数据初始化
for (int i=0; i<=row+1; i++)
for (int j=0; j<=col+1; j++)
{
if (0 == i || 0 == j || row < i || col < j)
map[i][j] = 1;
else
map[i][j] = 0;
}
int dir[8][2] = {{0,1},{1,0},{0,-1},{-1,0},{-1,0},{0,-1},{1,0},{0,1}};//指示旋转方向游标
int begin, end; //根据选择逆时针还是顺时针,确定4个旋转方向
int s, r, c;
if (0 == t)//逆时针
{
begin = 4;
end = 8;
}
else//顺时针
{
begin = 0;
end = 4;
}
s = r = c = map[1][1] = 1;
while (row * col > s)
{
for (int i=begin; i<end; i++) //每轮循环输入一条边,不断旋转,直到全部记录全部数字
{
while (0 == map[r+dir[i][0]][c+dir[i][1]]) //可输入数据
map[r+=dir[i][0]][c+=dir[i][1]] = ++s; //改变行,列下标的值,同时记录该点数据
}
}
for (int i=1; i<=row; i++) //输出矩阵
{
for (int j=1; j<=col; j++)
cout << setw(4) << map[i][j];
cout << endl;
}
cout << endl;
}