[ccf/csp题]201412-2 Z字形扫描

试题编号: 201412-2
试题名称: Z字形扫描
时间限制: 2.0s
内存限制: 256.0MB
问题描述
问题描述
  在图像编码的算法中,需要将一个给定的方形矩阵进行Z字形扫描(Zigzag Scan)。给定一个n×n的矩阵,Z字形扫描的过程如下图所示:

这里写图片描述

  对于下面的4×4的矩阵,
  1 5 3 9
  3 7 5 6
  9 4 6 4
  7 3 1 3
  对其进行Z字形扫描后得到长度为16的序列:
  1 5 3 9 7 3 9 5 4 7 3 6 6 4 1 3
  请实现一个Z字形扫描的程序,给定一个n×n的矩阵,输出对这个矩阵进行Z字形扫描的结果。
输入格式
  输入的第一行包含一个整数n,表示矩阵的大小。
  输入的第二行到第n+1行每行包含n个正整数,由空格分隔,表示给定的矩阵。
输出格式
  输出一行,包含n×n个整数,由空格分隔,表示输入的矩阵经过Z字形扫描后的结果。
样例输入
4
1 5 3 9
3 7 5 6
9 4 6 4
7 3 1 3
样例输出
1 5 3 9 7 3 9 5 4 7 3 6 6 4 1 3
评测用例规模与约定
  1≤n≤500,矩阵元素为不超过1000的正整数。


解析:

这道题,刚看到后有点懵,读完题还不太清楚题目里的Z字形到底是怎么个扫描法。刚开始还是没头绪的,但是自己在纸上画了3种情况,找到规律还是很容易解出来的。这道题思路有很多,比如你可以通过判断是否走到边界来判断是否要改变方向。但是我觉的这道题作为第2题,通过找规律解出来是最节省时间的。

我先来列举3种情况的走法:

n=3时:右1 -> 左下1 -> 下1 -> 右上2 -> 下1 -> 左下1 ->右1
n=4时:右1 -> 左下1 -> 下1 -> 右上2 -> 右1 -> 左下3 -> 右1 -> 右上2 -> 下1 -> 左下1 ->右1
n=5时:右1 -> 左下1 -> 下1 -> 右上2 -> 右1 -> 左下3 -> 下1 -> 右上4 -> 下1 -> 左下3 -> 右1 -> 右上2 ->下1 -> 左下1 -> 右1

规律就是:
总共移动的方向有4个,分别为向右,向左下,向下,向右上。
这4个方向可以分成2组,一组为向右和向下,每次都只移动1个步长。一组为左下和右上,
我们可以发现,运动方向刚开始是向右,然后是左下,下,右上。
斜方向的移动步长是先递增后递减。
拿n=5举例,斜方向的步长分别为: 1 2 3 4 3 2 1
方向的变换我们可以得出:右方向和下方向是交替的,但是有一个地方特殊,就是当移动完对角线时,方向是不变化的,我已经用粗体标出了。拿n=5举例吧,当第七步向下移动一个单位长度,再向右上方向移动4个单位长度后,按照之前的规律,紧接的一下步应该变成右1,但是没有发生改变。

左下和右上方向是一直交替进行的,并且每次都穿插在向右或向下移动之后。

所以我们可以分两步来遍历整个矩阵,沿右上方向的斜对角线分开,先遍历左半部分,再遍历右半部分。

代码如下:

#include<iostream>
using namespace std;


int main()
{
    ios::sync_with_stdio(false);

    int n;  //存储输入的n值
    cin>>n;
    int a[n][n];  //存储输入的矩阵

    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            cin>>a[i][j];
        }
    }
    int m,k;   //m记录访问的需要访问元素的横坐标,k为纵坐标
    m=k=0;
    cout<<a[k][m]<<" ";   //访问第一个元素
    int flag1=-1;    //记录访问方向的标志位,-1为向右,1为向下
    int flag2=-2;    //访问方向的标志位,-2为左下,2为右上
    int s1=1;      //记录左下和右上访问方向的步长
    for(int i=0;i<n-1;i++)   //先遍历左上半边
    {
        if(flag1==-1)  //向下访问
        {
            m++;    //对应横坐标加1
        }
        else     //向右访问
        {
            k++;
        }
        if(i!=n-2)     //如果不是最后一次,则将下次访问方向改变
        flag1=-flag1;

        cout<<a[k][m]<<" "; 

        for(int j=0;j<s1;j++)   //斜方向访问
        {
            if(flag2==-2)   //左下
            {
                m--;
                k++;
            }
            else     //右上
            {
                m++;
                k--;
            }
            cout<<a[k][m]<<" ";
        }
        if(i!=n-2)   //若不是最后一次,则下次步长加1,否则减1
        s1++;
        else
        s1--;

        flag2=-flag2;    //下次反问的斜方向发生改变
    }
    for(int i=0;i<n-1;i++)   //遍历右下半部分
    {
        if(flag1==-1)    
        {
            m++;
        }
        else
        {
            k++;
        }
        cout<<a[k][m]<<" ";

        flag1=-flag1;
        for(int j=0;j<s1;j++)
        {
            if(flag2==-2)
            {
                m--;
                k++;
            }
            else
            {
                m++;
                k--;
            }
            cout<<a[k][m]<<" ";

        }
        s1--; 
        flag2=-flag2;

    }
    return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值