栈的定义
栈可以定义为只允许在表的末端进行插入和删除的线性表。允许插入的一端称为栈顶,而不允许插入和删除的一端叫做栈底。栈有两种典型的存储表示,基于数组的存储表示和基于链表的存储表示。基于数组的存储表示实现的栈称为顺序栈,基于链表的存储表示实现的栈称为链式栈。
栈的应用
- 括号匹配
public void printMatchPairs(String expression) {
Stack<Integer> stack = new Stack<>();
int length = expression.length();
int j;
for (int i = 0; i < length; i++) {
// 左括号位置进栈
if (expression.charAt(i) == '(') {
stack.push(i);
}
if (expression.charAt(i) == ')') {
if (!stack.isEmpty()) {
// 退栈成功,匹配成功
j = stack.pop();
System.out.println(i + "与" + j + "匹配");
} else {
System.out.println("没有与" + i + "匹配的左括号");
}
}
}
// 栈中还有左括号
while (!stack.isEmpty()) {
j = stack.pop();
System.out.println("没有与" + j + "匹配的右括号");
}
}
- 表达式的计算
/**
* 计算后缀表达式的值
*
* @param expression 后缀表达式
* @return 计算结果
*/
public int calculate(String expression) {
int length = expression.length();
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < length; i++) {
char character = expression.charAt(i);
// 如果式操作数则入栈
if (Character.isDigit(character)) {
stack.push(Character.getNumericValue(character));
} else {
// 如果是操作符,则退出两个操作数
// 计算结果后再入栈
if (stack.size() >= 2) {
int rightOperator = stack.pop();
int leftOperator = stack.pop();
switch (character) {
case '+':
stack.push(leftOperator + rightOperator);
break;
case '-':
stack.push(leftOperator - rightOperator);
break;
case '*':
stack.push(leftOperator * rightOperator);
break;
case '/':
if (rightOperator == 0) {
throw new IllegalArgumentException("divided 0");
}
stack.push(leftOperator / rightOperator);
break;
}
}
}
}
if (!stack.isEmpty()) {
return stack.pop();
}
return 0;
}
- 中缀表达式转后缀表达式
public String postFix(String expression) {
// 中缀表达式转后缀表达式,需要考虑操作符的
// 栈内和栈外的优先级
Map<Character, Integer> ispMap = new HashMap<>();
ispMap.put('#', 0);
ispMap.put('(', 1);
ispMap.put('*', 5);
ispMap.put('/', 5);
ispMap.put('%', 5);
ispMap.put('+', 3);
ispMap.put('-', 3);
ispMap.put(')', 6);
Map<Character, Integer> icpMap = new HashMap<>();
icpMap.put('#', 0);
icpMap.put('(', 6);
icpMap.put('*', 4);
icpMap.put('/', 4);
icpMap.put('%', 4);
icpMap.put('+', 2);
icpMap.put('-', 2);
icpMap.put(')', 1);
Stack<Character> stack = new Stack<>();
StringBuilder stringBuilder = new StringBuilder();
stack.push('#');
int i = 0;
while (!stack.isEmpty()) {
char character = expression.charAt(i);
// 如果是操作数,直接输出
if (Character.isAlphabetic(character) && Character.isDigit(character)) {
stringBuilder.append(character);
i++;
} else {
char top = stack.peek();
// 新输入的操作符优先级高,则入栈
if (ispMap.get(top) < icpMap.get(character)) {
stack.push(character);
i++;
} else if (ispMap.get(top) > icpMap.get(character)) {
// 新输入操作符优先级低,则出栈
stringBuilder.append(stack.pop());
} else {
if (stack.pop() == '(') {
i++;
}
}
}
}
return stringBuilder.toString();
}
栈与递归
若一个对象部分的包含它自己,或用它自己给自己定义,则称这个对象是递归的;而且如果一个过程直接或者间接地调用自己,则称这个过程是递归的。
对于一个比较复杂的问题,如果能够分解成几个相对简单的且解法相同或类似的子问题时,只要解决了这些子问题,那么原问题就迎刃而解了。当分解后的子问题可以直接解决时,就停止分解。递归定义的函数可以用递归过程来编程求解。
- 数据结构是递归的。
比方说链表这中数据结构的定义就是递归的。 - 问题解法是递归的。
用递归来求解汉诺塔问题。
public void hanoi(int n, String A, String B, String C) {
// 如果只有一个盘子
// 直接从A柱移动到C柱
if (n == 1) {
System.out.println("Move top disk from " + A + " to " + C);
} else {
// 借助C柱,将A柱上的上面
// n-1个盘子移动到B柱
hanoi(n - 1, A, C, B);
System.out.println("Move top disk from " + A + " to " + C);
// 借助A柱,将B柱上的
// n-1个盘子移动到C柱
hanoi(n - 1, B, A, C);
}
}
- 递归与递归工作栈。
外部调用结束后,将返回调用递归过程的主函数。内部调用结束后,将返回到递归过程本地调用语句的后继语句处。在每进入一层递归时,系统就要建立一个新的工作记录,包括返回地址、实参的副本、本层的局部变量,并加入到递归工作栈的栈顶。每退出一层递归,就从递归工作栈中退出一个工作记录。 - 用栈实现递归过程的非递归算法。
class Node {
public long n;
public int tag;
public Node(long n, int tag) {
this.n = n;
this.tag = tag;
}
}
public long fibnacci(long n) {
Stack<Node> stack = new Stack<>();
long sum = 0;
do {
while (n > 1) {
Node node = new Node(n, 1);
stack.push(node);
n--;
}
sum = n + sum;
while (!stack.isEmpty()) {
Node top = stack.pop();
if (top.tag == 1) {
// 向右递归
top.tag = 2;
stack.push(top);
n = top.n - 2;
break;
}
}
} while (!stack.isEmpty());
return sum;
}
- 用迭代实现递归过程。
public long fibIter(long n) {
if (n <= 1) {
return n;
}
long twoBack = 0;
long oneBack = 1;
long current = 0;
for (int i = 2; i <= n; i++) {
current = oneBack + twoBack;
twoBack = oneBack;
oneBack = current;
}
return current;
}
一般对于尾递归或单向递归的情形,都可以利用迭代的方法,将递归过程改为非递归过程。
用回溯法求解迷宫问题
回溯法也称试探法。用回溯法求解问题时常常使用递归方法进行试探,或使用栈帮助向前试探和回溯。
public class Maze {
class Move {
public int xOffset;
public int yOffset;
public String dir;
public int dirNum;
public Move(int xOffset, int yOffset, String dir, int dirNum) {
this.xOffset = xOffset;
this.yOffset = yOffset;
this.dir = dir;
this.dirNum = dirNum;
}
}
class Item {
public int x;
public int y;
public int dir;
}
private int[][] maze;
private int[][] mark;
private int m;
private int p;
private Move[] moves = new Move[] {new Move(-1, 0, "N", 0),
new Move(-1, 1, "NE", 1), new Move(0, 1, "E", 2),
new Move(1, 1, "SE", 3), new Move(1, 0, "S", 4),
new Move(1, -1, "SW", 5), new Move(0, -1, "W", 6),
new Move(-1, -1, "NW", 7)};
public Maze(int[][] maze, int[][] mark, int m, int p) {
this.maze = maze;
this.mark = mark;
this.m = m;
this.p = p;
}
public int seek(int x, int y) {
int g, h;
String dir;
// 到达出口
if (x == m && y == p) {
return 1;
}
for (int i = 0; i < moves.length; i++) {
g = x + moves[i].xOffset;
h = y + moves[i].yOffset;
dir = moves[i].dir;
// 找下一个位置和方向
if (maze[g][h] == 0 && mark[g][h] == 0) {
mark[g][h] = 1;
if (seek(g, h) == 1) {
System.out.println(g + h + dir);
}
return 1;
}
// 回溯,换一个方向再试探
}
if (x == 1 && y == 1) {
System.out.println("not path in maze");
}
return 0;
}
public void path(int m, int p) {
int i, j, d, g, h;
mark[1][1] = 1;
Stack<Item> stack = new Stack<>();
Item temp = new Item();
temp.x = 1;
temp.y = 0;
temp.dir = 2;
stack.push(temp);
while (!stack.isEmpty()) {
temp = stack.pop();
i = temp.x;
j = temp.y;
d = temp.dir;
while (d < 8) {
// 找下一个位置
g = i + moves[d].xOffset;
h = j + moves[d].yOffset;
// 到达出口
if (g == m && h == p) {
System.out.println(i + j + d);
System.out.println(m + p);
return;
}
// 新的位置可通
if (maze[g][h] == 0 && mark[g][h] == 0) {
mark[g][h] = 1;
// 记忆已经通过的位置和前进方向
temp.x = i;
temp.y = j;
temp.dir = d;
stack.push(temp);
// 移动到(g,h),再各个方向试探
i = g;
j = h;
d = 0;
} else {
d++;
}
}
}
System.out.println("no path to maze");
}
}