数据结构-堆栈(2)
堆栈的应用
1.进制转换
- 进制转换是一种常见的数值计算问题,例如将十进制数转换成八进制数。 将十进制数2007转换成八进制数为3727。运算过程如图
- 可以看到上述过程是从低位到高位产生 8 进制数的各个数位,而在输出时,一般来说都 是从高位到低位进行输出,这正好产生数位的顺序相反。换一个说法就是,越晚生成的数位 越早需要输出,结果数位的使用具有后出现先使用的特点,因此生成的结果数位可以使用一个堆栈来存储,然后从栈顶开始依次输出即可得到相应的转换结果.
实现代码如下
/**
* 十进制数到八进制数
*
* @param i
* @return
*/
public int baseConversion(int i) {
Stack s = new StackSLinked();
while (i > 0) {
s.push(i % 8 + "");
i = i / 8;
}
String num = "";
while (!s.isEmpty()) {
num += s.pop();
}
return Integer.parseInt(num);
}
2.括号匹配检测
- 假设表达式中包含三种括号:圆括号、方括号和花括号,并且它们可以任意相互嵌套。 例如{{}}或[{()[]}]等为正确格式,而{[( ])}或({[()})等均为不正确的格式。
- 该问题可按“期待匹配消解”的思想来设计算法,对表达式中的每一个左括号都期待一个 相应的右括号与之匹配,表达式中越迟出现并且没有得到匹配的左括号期待匹配的程度越高。不是期待出现的右括号则是非法的。它具有天然的后进先出的特点。
- 可以如下设计算法:算法需要一个堆栈,在读入字符的过程中,如果是左括号,则 直接入栈,等待相匹配的同类右括号;若读入的是右括号,且与当前栈顶左括号匹配,则将栈顶左括号出栈,如果不匹配则属于不合法的情况。另外如果碰到一个右括号,而堆栈为空,说明没有左括号与之匹配,属于非法情况;或者字符读完,而堆栈不为空,说明有左括号没有得到匹配,也属于非法情况。当字符读完同时堆栈为空,并且在匹配过程中没有发现不匹配的情况,说明所有的括号是匹配的。
实现代码如下
/**
* 括号匹配检测
*
* @param str 字符串
* @return
*/
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.迷宫求解
- 求解从迷宫中的起点到某个终点的路径是一个有趣的问题,如下图所示。使用计算机 求解迷宫问题时,通常采用的方法是系统的尝试所有可能的路径:即从起点出发,顺着某个方向向前探索,例如向当前位置的左边探索, 若当前位置除向左之外还有其他方向的没有 被访问过的邻接点,则在向左探索之前,按固定的次序记录下当前位置其他可能的探索方向;若当前位置向左不能再走下去,则换到当前位置的其他方向进行探索;如果当前位置所有方向的探索均结束,却没有到达终点,则沿路返回当前位置的前一个位置,并在此位置还没有探索过的方向继续进行探索;直到所有可能的路径都被探索到为止。
- 为了保证在任何位置上都能原路返回,因此需要利用堆栈后进先出的特性来保存从起点到当前位置的路径以及在路径上各位置还可能进行探索的方向。
- 首先在计算机中可以使用一个二维字符数组来表示上图所示的迷宫。我们使用字符'1' 来表示迷宫中的墙体,即灰色的方块;用字符'0'来表示迷宫中可以通过的道路,即白色的方块。
首先定义迷宫的每一个单元
package com.wjy.Data_Structure.stack.application;
//迷宫单元的定义
public class Cell {
public int x = 0;
public int y = 0;
public boolean visited = false;// 是否访问过
public 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;
}
}
算法描述:
初始化,将起点加入堆栈;
while(堆栈不空){
取出栈顶位置作为当前位置;
如果 当前位置是终点,
则 使用堆栈记录的路径标记从起点至终点的路径;
否则{ 按照向下、右、上、左的顺序将当前位置下一个可以探索的位置入栈;
//从堆栈取出的探索方向顺序则是左、上、右、下
如果 当前位置没四周均不可通
则 当前位置出栈;
}
}
代码实现:
package com.wjy.Data_Structure.stack.application;
import com.wjy.Data_Structure.stack.Stack;
import com.wjy.Data_Structure.stack.StackSLinked;
public class CellMain {
/**
* 创建迷宫
*
* @param maze
* @return
*/
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;
}
/**
* 打印迷宫
*
* @param cells
*/
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();
}
}
/**
* 判断是否为通路且未走过
*
* @param cell
* @return
*/
private boolean isValidWayCell(Cell cell) {
return cell.c == '0' && !cell.visited;
}
/**
* 判断是否是相邻的单元
*
* @return
*/
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;
}
/**
* 找到从起点到终点的路径
*
* @param maze迷宫的字符数组
* @param sx
* @param sy起点坐标
* @param ex
* @param ey终点坐标
*
*
*/
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 = '*';
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();
}
}
System.out.println("没有从起点到终点的路径。");
}
}
测试代码:
package com.wjy.Data_Structure.stack.application;
import java.util.Random;
import org.junit.Test;
public class CellMainTest {
@Test
public void testMazeExit() {
CellMain m = new CellMain();
char cells[][] = new char[11][11];
Random random = new Random();
for (int i = 0; i < cells.length; i++) {
for (int y = 0; y < cells[i].length; y++) {
cells[i][y] = '0';
}
}
for (int i = 0; i < 11; i++) {
cells[i][0] = '1';
cells[i][10] = '1';
cells[0][i] = '1';
cells[10][i] = '1';
cells[random.nextInt(10)][random.nextInt(10)] = '1';
}
m.mazeExit(cells, 1, 1, 8, 8);
}
}
测试结果: