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

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

posted @ 2011-09-02 20:14  xiaodongrush  阅读(1564)  评论(2编辑  收藏  举报