关于堆栈与队列使用的小思考:
从经典的问题开始
1.走迷宫问题:
首先还是说说问题大概情况,走迷宫问题从入口出出发,顺着周围可走的通道向前探索,目的很简单到达出口就行了.估计你也许听得不耐烦了,好吧,接下来让我们直截了当地说明它在C语言里面的数据描述。
我们用一个二维的数组表示的矩阵来表示一个平面上的迷宫,其中数组的每一个元素代表了一个通道,如果值为1则表示有障碍,为0则表示可通。为了方便在迷宫的最外面设置一层设置了障碍。因此虽然迷宫的大小是10*15而数组的大小却是12*17。
其中迷宫的入口位于数组的(1,1)处,而出口位于(10,15)处。用dx,dy表示搜索方向上的变化。
OK!接下来就让我们一起来走这个抽象的迷宫~~~~
#define MU 10 //迷宫的行数
#define NU 15 //迷宫的列数
int Labyrinth[MU+2][NU+2]={{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
//移动方向上的增量
// 东 东南 南 西南 西 西北 北 东北
int dx[DIRECTIONS]={ 0, 1, 1, 1, 0, -1, -1, -1};
int dy[DIRECTIONS]={ 1, 1, 0, -1, -1, -1, 0, 1};
解决方案:
一般采用穷举搜索这种通用比较强,却在某种程度上牺牲了不少时间和内存空间的方法。
简单地说就是可通的话就走,如果有障碍的话就拉倒,并且返回上一步换一个方向之后继续向前探索,直到出口。
因为问题中需要保存已经走过的路径,所以一般大家第一个想法也许都会想到了用堆栈来解决,我的教科书上也使用了用堆栈解决的方法,今天我们不用堆栈来解决,换用队列来解决。
#define MAX_SIZE 400;
Struct node
{
int x,y; //搜索时当前通道的位置
int pre; //上一个通道在队列中的位置
}Queue[MAX_SIZE];
int gotoLabyrinth ( )
{
int i, j, x, y, v ; //(i,j)
int front , rear, finded ;
Queue[1].x=1; Queue[1].y=1; Queue[1].pre=0;
front=1;rear=1; Labyrinth[1][1]=-1;
while(front<=rear)
{
x= Queue[front].x;
y= Queue[front].y;
for(v=1;v<=8;v++) //循环扫描每个方向
{
i=x+zx[v]; //选择一个前进方向i,j
j=x+zy[v];
if(Labyrinth[i][j]==0) //如果可走
{
rear++; //入队尾
Queue [rear].x=i;
Queue[rear].y=j;
Queue[rear].pre=front;
Labyrinth[i][j]=-1; //已经访问过了
} `
if(i==m&&j==n) return 1;
}
front++;
}
return 0;
}
接下来轮到我们经典堆栈的应用了
struct node
{
int x,y; //所在迷宫的当前的坐标位置
int direction; //下一个位置的方向
} Stack[MAX_SIZE]; // 程序中用到的堆栈
int gotoLabyrinth()
{
int i = 1,j = 1;
do
{
if( Labyrinth[i][j] == 0)
{
push(i,j,0);
Labyrinth[i][j] = -1;
if( i==MU && j==NU ) return (1);
else
{
i += dx[Stack[top].direction]; //向正东方继续搜索
j += dy[Stack[top].direction];
}
}
else
{
if( top!=0 && stack[top].direction< DIRECTIONS-1 )
{
Stack[top].direction++; //切换一个方向之后继续搜索
i=Stack[top].x+ dx[Stack[top].direction];
j=Stack[top].y+ dy[Stack[top].direction];
}
if(Stack[top].direction >= DIRECTIONS-1&&top!=0)
{
top--; //如果此路不通,退栈之后,换一条路
}
}
}while(top!=0);
return (0);
}
堆栈和队列在迷宫问题里面的应用,就是其在图的遍历问题中的应用,这些都反映了堆栈和队列应用的本质。
图的遍历
问题描述:
假设图采用邻接表的存储结构
定义如下:
#define MAXVEX 100
struct edgenode
{
int adjvex; //邻接点序号
char info; // 邻接点信息
struct edgenode *next;
};
struct vexnode
{
char data; //节点信息
struct edgenode *link;
};
typedef struct vex node adjlist[MAXVEX];
相信上面的图的描述方式大家都不会陌生
1. 深度优先搜索法
利用堆栈保存遍历过的节点信息
void dfs(adjlist g, int v, int n)
{
struct edgenode *stack[MAXVEX],*P;
int visited[MAXVEX] ,i;
int top=0;
for (i=o;i<n;i++) visited[i]=0;
printf(“%d”,v);
p=g[v].link;
while(top>0 || p!=NNLL)
{
while(p!=NULL)
if(visited[p->adjvex]==1) //如果已经访问过了
p=p->next;
else
{
printf(“%d”,p->adjvex);
visited[p->adjvex]=1;
top++; //将访问的节点入栈
stack[top]=p;
p=g[p->adjvex].link;
}
if(top>0)
{
p=stack[top];
top--; //退一步找,回溯查找被访问过的节点的未被访问过的邻接点
p=p->next;
}
}
}
2. 宽度优先搜索法
采用队列来解决问题
int visited[MAXVEX];
int queue[MAXVEX];
void bfs(adjlist adj, int vi )
{
int front=0,rear=1; //队列的头和尾部
int v,i;
struct edgenode *p;
for(i=0;i<MAXVEX;i++)
{
visited[vi]=1;
printf(“%d”,v);
queue[rear]=vi;
while(front!=rear)
{
front =(front+1) %MAXVEX;
v=queue[front];
p=adj[v].link; //找到V的邻接点并且访问所有未被访问过的邻接点
while(p!=NULL)
{
if(visited[p->adjvex]==0) //若未被访问则置访问标志且入队列尾
{
visited[p->adjvex.]==1;
printf(“%d”,p->adjvex);
rear=(rear+1) %MAXVEX;
queue[rear]=p->adjvex;
}
p=p->next;
}
}
}
}
小结:相信不用我说任何人都已经知道了,迷宫的堆栈解法就是图的深度遍历问题,迷宫的队列解法其实就是图的宽度遍历问题,在编程的过程中可以发现其实还有很多问题都可以抽象成对于图的操作问题,看来啊学好了数据结构真的可以解决不少实际问题。