螺旋队列-打印-查找-步数解法-边界解法

1. 简述

    螺旋队列也是常考题目之一,被程序员面试宝典收录了。这里写一写我对螺旋队列的理解,两种螺旋队列,两种操作。
    两种队列的不同点是:一个从左上角开始扩展,一个从中间开始扩展。相同点是:初始点的数值都是1,数值逐渐增加,而且都是沿着右下左上的顺序进行扩展的。螺旋队列举例,如下所示:
    第一种螺旋队列:                           第二种螺旋队列:
    1    2    3    4                              7    8    9  10
    12 13  14   5                               6    1    2  11
    11 16  15   6                               5    4    3  12
    10   9   8    7                              16 15   14  13   
    两种操作,打印和查找。打印:指定矩阵长度N,将矩阵打印出来,例如指定N=4,就要输出上面的矩阵。查找:螺旋队列中数值为1的元素所在的左边认为是(0,0),给出一个坐标(x,y)找到该坐标位置上的值,例如指定坐标为(2,2),第一个队列返回的值为15,第二个队列返回的值为13。

2. 第一种螺旋队列   

    一般考察的是打印操作。使用模拟的方法,开辟一个A[N][N]的数组,按照数值的顺序,逐个将1,2,3,...,N*N放到数组的合适位置中。放置的方法就是,先把1放进去,然后先向右走,可以走就放进去,不能走就转向下走,直到不能走就向左,然后向上,最后再向右依次类推。判断能不能走可以使用四个边界,比如RightBound,初始值是3,即从A[0][0],最多可以走到A数值4的位置,此后修改RightBound的值为2,因为下次做多走到数值14的位置,其他方向的初始值类似。
    对于N的初始值,向左的边界为0,向上的边界为1,向右的边界为N-1,向下的边界为N-1。注意上边界初始不能是0,否则就又回到原点了,会出现错误的。   

#include  < iostream >
using   namespace  std;

#define  N 5
#define  RIGHT 0
#define  DOWN 1
#define  LEFT 2
#define  UP 3

int  main() {
  
int  A[N][N];
  
//  get A
   int  RightBound,DownBound,LeftBound,UpBound;
  RightBound 
=  N - 1 ; DownBound  =  N - 1 ; LeftBound  =   0 ; UpBound  =   1 ;  // 注意UpBound初始值为1
  
int  direction, x, y, value, maxValue;
  A[
0 ][ 0 =   1 ; x  =  y  =   0 ; value   =   1 ; direction  =  RIGHT; maxValue  = N * N;
  
while (value  <=   24 ) {  // 结束值不能错,否则会产生错误的
    
switch (direction) {
      
case  RIGHT:
        
if (y + 1   >  RightBound) {
          direction 
=  (direction + 1 %   4 ;
          RightBound
-- ;
        }
        
else
          A[x][
++ y]  =   ++ value;
        
break ;
      
case  DOWN:
        
if (x + 1   >  DownBound) {
          direction 
=  (direction + 1 %   4 ;
          DownBound
-- ;
        }
        
else  
          A[
++ x][y]  =   ++ value;
        
break ;
      
case  LEFT:
        
if (y - 1   <  LeftBound) {
          direction 
=  (direction + 1 %   4 ;
          LeftBound
++ ;
        }
        
else  
          A[x][
-- y]  =   ++ value;
        
break ;
      
case  UP:
        
if (x - 1   <  UpBound) {
          direction 
=  (direction + 1 %   4 ;
          UpBound
++ ;
        }
        
else  
          A[
-- x][y]  =   ++ value;
        
break ;
    }
 
//  cout << x << ", " << y << ", direction:" << direction <<endl;
  }
  
//  print A
   for ( int  i = 0 ; i < N; i ++ ) {
    
for ( int  j = 0 ; j < N; j ++ ) {
      
if (A[i][j]  <   10 )
        cout 
<<   "   "   <<  A[i][j]  <<   "   " ;
      
else  
        cout 
<<  A[i][j]  <<   "   " ;
    }
    cout 
<<  endl;
  }
  system(
" PAUSE " );
  
return   0 ;

    输出结果如下:
   
    对于查找问题,可以直接把能够包含指定坐标的矩阵构造出来,然后把坐标上的值返回,比如指定坐标为(4,5),那么矩阵N=max{4,5}+1=6,开辟一个int A[6][6]的数组,把数值填充好了,然后返回A[4][5]就好了。不过这样会浪费很大的存储的空间,实际上,构造矩阵的过程中,每次根据方向和方向界限可以得到坐标和数值的对应,因此,不用开辟A[6][6],循环部分保留,每次检查一下当前坐标是否是指定的坐标,如果是的话,就把对应数值输出,否则继续循环。
    由于在循环中,数值和坐标是对应产生的,实际上如果题目变为给数值,找坐标,也是一样的。
    考察具体分有三种:打印矩阵,给坐标找数值,给数值找坐标,核心在于预测队列的从(0,0)开始的每一步的坐标,因为矩阵的长度和每一步的数值都十分要预测,下一步坐标是通过右左下上的顺序和对应边界的更新实现的。

3. 第二种螺旋队列

    前面已经分析了,无论是打印矩阵,还是给坐标找数值,还是给数值找坐标,管家你是预测队列从(0,0)开始的每一步的坐标。第一种螺旋队列的预测是一种模拟的方法,即试着取走,如果碰过边界就转方向。第二种螺旋队列的预测,我还不知道怎么进行模拟,不过有公式可以用,即方向是右下左上,步数依次是1,1,2,2,3,3,4,4,5,5...。
    另外螺旋队列打印的话,需要首先计算一下中心的坐标,所以对于这种队列,题目一般考察给坐标预测值,降低了程序的复杂度。

    代码实现:

#include  < iostream >
using   namespace  std;

#define  N 4
#define  RIGHT 0
#define  DOWN 1
#define  LEFT 2
#define  UP 3

bool  check( int  x, int  y,  int  index_x,  int  index_y) {
  
return  (x == index_x && y == index_y);
}

int  main() {
  
int  RightNum, DownNum, LeftNum, UpNum;
  RightNum 
=  DownNum  =   1 ;
  LeftNum 
=  UpNum  =   2 ;
  
int  index_x  =   2 , index_y  =   1 //  坐标(2,1)
   int  value, x, y, direction;
  x 
=  y  =   0 ; direction  =  RIGHT; value  =   1 ;
  
bool  found  =  check(x,y,index_x,index_y);  //  检查一下初始点 
   while ( ! found) {
    
if (value  >=   25 )
      
break ;
    
switch (direction) {
      
case  RIGHT:
        
for ( int  i = 0 ; i < RightNum; i ++ ) {
          
++ x;  ++ value;
          found 
=  check(x,y,index_x,index_y);
          
if (found)
            
break ;
        }
        RightNum 
+=   2 ;
        direction 
=  (direction  +   1 %   4 ;
        
break ;
      
case  DOWN:
        
for ( int  i = 0 ; i < DownNum; i ++ ) {
          
-- y;  ++ value;
          found 
=  check(x,y,index_x,index_y);
          
if (found)
            
break ;          
        }
        DownNum 
+=   2 ;
        direction 
=  (direction  +   1 %   4 ;       
        
break ;
      
case  LEFT:
        
for ( int  i = 0 ; i < LeftNum; i ++ ) {
          
-- x;  ++ value;
          found 
=  check(x,y,index_x,index_y);
          
if (found)
            
break ;
        }
        LeftNum 
+=   2 ;
        direction 
=  (direction  +   1 %   4 ;  
        
break ;
      
case  UP:
        
for ( int  i = 0 ; i < UpNum; i ++ ) {
          
++ y;  ++ value;
          found 
=  check(x,y,index_x,index_y);
          
if (found)
            
break ;
        }
        UpNum 
+=   2 ;
        direction 
=  (direction  +   1 %   4 ;  
        
break ;
    }
  }
  cout 
<<  value  <<  endl;
  system(
" PAUSE " );
  
return   0 ;
}

    输出结果为:10。

4. 重点分析
4.1 x,y随方向的变化
    第一个螺旋队列,坐标原点在左上角,x,y是数组的下标,x表示数组行,是纵坐标,y表示数组列,是横坐标。向右++y,向左--y,向上--x,向下++x。
    第二个螺旋队列,坐标原点在右下角,x表示横坐标,y表示纵坐标。向右++x,向左--x,向上++y,向下--y。 

4.2 边界与步数

    第一个队列用的是边界和方向,第二个队列用的是步数与方向,实际上这两种方法在两个队列上都可以用。
    · 第一个队列边界+方向
    从(0,0)开始,方向顺序为右下左上,初始边界为RighBound=N-1,DownBound=N-1,LeftBound=0,UpBound=1,边界更新为RightBound--,DownBound--,LeftBound++,UpBound++。  

    · 第一个队列步数+方向
    从(-1,0)开始,方向顺序为右上左下,初始步数N,N-1,N-1,N-2,N-2,步数更新每次递减2。以N=4为例,步数序列为4,3,3,2,2,1,1,1。
    · 第二个队列边界+方向
    从(0,0)开始,方向顺序为右下左上,初始边界为RightBound=1,DownBound=1,LeftBound=-1,UpBound=-1,边界更新为RightBound++,DownBound++,LeftBound--,UpBound--。
    · 第二个队列步数+方向
    从(0,0)开始,方向顺序为右下左上,初始步数1,1,2,2,步数更新每次递增2。以N=4为例,步数序列为1,1,2,2,3,3,4。
 5. 参考

    程序员面试宝典(第二版)92页
    螺旋矩阵的算法    http://www.cppblog.com/issayandfaye/archive/2009/11/15/100976.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值