三 、堆栈的应用
堆栈所具有的后进先出特性,使得堆栈成为程序设计中非常有用的工具。
3.1进制转换
进制转换是一种常见的数值计算问题,例如将十进制数
例如:(2007)10= (3727)8,其运算过程如下:
上述过程是从低位到高位产生8进制数的各个数位,而在输出时,一般来说都是从高位到低位进行输出,这正好产生数位的顺序相反。换一个说法就是,越晚生成的数位越早需要输出,结果数位的使用具有后出现先使用的特点,因此生成的结果数位可以使用一个堆栈来存储,然后从栈顶开始依次输出即可得到相应的转换结果。
// 进制转换
public void baseConversion(int i) {
Stack s = new StackSLinked();
while (i > 0) {
s.push(i % 8 + "");
i = i / 8;
}
while (!s.isEmpty())
System.out.print((String) s.pop());
}
3.2括号匹配检测
假设表达式中包含三种括号:圆括号、方括号和花括号,并且它们可以任意相互嵌套。例如{[{}]([])}或[{()[]}]等为正确格式,而{[( ])}或({[()})等均为不正确的格式。
该问题可按“期待匹配消解”的思想来设计算法,于是可以如下设计算法:算法需要一个堆栈,在读入字符的过程中,如果是左括号,则直接入栈,等待相匹配的同类右括号;若读入的是右括号,且与当前栈顶左括号匹配,则将栈顶左括号出栈,如果不匹配则属于不合法的情况。另外如果碰到一个右括号,而堆栈为空,说明没有左括号与之匹配,属于非法情况;或者字符读完,而堆栈不为空,说明有左括号没有得到匹配,也属于非法情况。当字符读完同时堆栈为空,并且在匹配过程中没有发现不匹配的情况,说明所有的括号是匹配的。
// 符号匹配
public boolean bracketMatch(String str) {
Stack s = new StackSLinked();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
switch (c) {
case '{':
case '[':
case '(':
s.push(Integer.valueOf(c));
break;
case '}':
if (!s.isEmpty() && ((Integer) s.pop()).intValue() == '{')
break;
else
return false;
case ']':
if (!s.isEmpty() && ((Integer) s.pop()).intValue() == '[')
break;
else
return false;
case ')':
if (!s.isEmpty() && ((Integer) s.pop()).intValue() == '(')
break;
else
return false;
}
}
if (s.isEmpty())
return true;
else
return false;
}
3.3 迷宫求解
求解从迷宫中的起点到某个终点的路径的问题。使用计算机求解迷宫问题时,通常采用的方法是系统的尝试所有可能的路径:即从起点出发,顺着某个方向向前探索,例如向当前位置的左边探索,若当前位置除向左之外还有其他方向的没有被访问过的邻接点,则在向左探索之前,按固定的次序记录下当前位置其他可能的探索方向;若当前位置向左不能再走下去,则换到当前位置的其他方向进行探索;如果当前位置所有方向的探索均结束,却没有到达终点,则沿路返回当前位置的前一个位置,并在此位置还没有探索过的方向继续进行探索;直到所有可能的路径都被探索到为 止。
为了保证在任何位置上都能原路返回,因此需要使用一个后进先出的存储结构来保存从起点到当前位置的路径以及在路径上各位置还可能进行探索的方向。因此在迷宫问题中使用堆栈是自然的。
首先在计算机中可以使用一个二维字符数组来表示上图所示的迷宫。我们使用字符'1'来表示迷宫中的墙体,即灰色的方块;用字符'0'来表示迷宫中可以通过的道路,即白色的方块。按上述方法,上图所示的迷宫可以用二维字符数组表示。
其次,求解迷宫的算法思想可以描述为:
初始化,将起点加入堆栈;
while(堆栈不空){
取出栈顶位置作为当前位置;
if (当前位置是终点)
使用堆栈记录的路径标记从起点至终点的路径;
else
{
按照向下、右、上、左的顺序将当前位置下一个可以探索的位置入栈;
//从堆栈取出的探索方向顺序则是左、上、右、下
if (当前位置没四周均不可通)
当前位置出栈;
}
}
迷宫中当前位置的下一个可以探索的点是未曾走到过的位置及其有待探索的下一个位置,即要求该位置不但是通道块,而且不在当前路径上,也不是曾经纳入到路径中或有待探索的通道块。这可以通过对每一个位置设置一个标志,来表明该位置是否可以作为下一个可探索的位置。
为了在算法中可以对每一个位置进行操作,下面先定义迷宫中的每一个位置。
package stack;
//迷宫单元的定义
class Cell {
int x = 0; // 单元所在行
int y = 0; // 单元所在列
boolean visited = false; // 是否访问过
char c = ' '; // 是墙('1')、可通路('0')或起点到终点的路径('*')
public Cell(int x, int y, char c, boolean visited) {
this.x = x;
this.y = y;
this.c = c;
this.visited = visited;
}
}
迷宫算法如下:
package stack;
public class Maze {
public void mazeExit(char[][] maze, int sx, int sy, int ex, int ey) {
Cell[][] cells = createMaze(maze); // 创建化迷宫
printMaze(cells); // 打印迷宫
Stack s = new StackSLinked(); // 构造堆栈
Cell startCell = cells[sx][sy]; // 起点
Cell endCell = cells[ex][ey]; // 终点
s.push(startCell); // 起点入栈
startCell.visited = true; // 标记起点已被访问
while (!s.isEmpty()) {
Cell current = (Cell) s.peek();
if (current == endCell) { // 路径找到
while (!s.isEmpty()) {
Cell cell = (Cell) s.pop();// 沿路返回将路径上的单元设为*
cell.c = '*';
// 堆栈中与cell 相邻的单元才是路径的组成部分,除此之外,
// 堆栈中还有记录下来但是未继续向下探索的单元,
// 这些单元直接出栈
while (!s.isEmpty() && !isAdjoinCell((Cell) s.peek(), cell))
s.pop();
}
System.out.println("找到从起点到终点的路径。");
printMaze(cells);
return;
} else { // 如果当前位置不是终点
int x = current.x;
int y = current.y;
int count = 0;
if (isValidWayCell(cells[x + 1][y])) { // 向下
s.push(cells[x + 1][y]);
cells[x + 1][y].visited = true;
count++;
}
if (isValidWayCell(cells[x][y + 1])) { // 向右
s.push(cells[x][y + 1]);
cells[x][y + 1].visited = true;
count++;
}
if (isValidWayCell(cells[x - 1][y])) { // 向上
s.push(cells[x - 1][y]);
cells[x - 1][y].visited = true;
count++;
}
if (isValidWayCell(cells[x][y - 1])) { // 向左
s.push(cells[x][y - 1]);
cells[x][y - 1].visited = true;
count++;
}
if (count == 0)
s.pop();// 如果是死点,出栈
}// end of if
}// end of while
System.out.println("没有从起点到终点的路径。");
}
private void printMaze(Cell[][] cells) {
for (int x = 0; x < cells.length; x++) {
for (int y = 0; y < cells[x].length; y++)
System.out.print(cells[x][y].c);
System.out.println();
}
}
private boolean isAdjoinCell(Cell cell1, Cell cell2) {
if (cell1.x == cell2.x && Math.abs(cell1.y - cell2.y) < 2)
return true;
if (cell1.y == cell2.y && Math.abs(cell1.x - cell2.x) < 2)
return true;
return false;
}
private boolean isValidWayCell(Cell cell) {
return cell.c == '0' && !cell.visited;
}
private Cell[][] createMaze(char[][] maze) {
Cell[][] cells = new Cell[maze.length][];
for (int x = 0; x < maze.length; x++) {
char[] row = maze[x];
cells[x] = new Cell[row.length];
for (int y = 0; y < row.length; y++)
cells[x][y] = new Cell(x, y, maze[x][y], false);
}
return cells;
}
}
以(8, 8)为起点坐标、以(1, 7)为终点坐标作为输入,算法的输出如图所示。