栈的特点
- 栈属于线性序列结构。可基于链表或者向量结构实现。
- 栈中元素的操作次序始终遵循“后进先出”(last-in-first-out)
/**
*基于链表实现栈
*/
import java.util.LinkedList;
public class StackBaseLinkedList<E> {
private LinkedList<E> storage = null;
public StackBaseLinkedList() {
storage = new LinkedList<E>();
}
public int size() {
return storage.size();
}
public boolean empty() {
return storage.isEmpty();
}
/**
* LinkedList中于栈相关的源码可以看出Java官方的栈底是first,栈顶是last。
* 将对象压入栈
* */
public void push(E e) {
storage.addFirst(e);
}
/**
* 返回并删除栈顶对象
* */
public E pop() {
return storage.removeFirst();
}
/**
* 引用栈顶对象但是不删除
* */
public E top() {
return storage.getFirst();
}
}
函数调用栈
在windos等大部分操作系统中,每个运行中的二进制程序都配有一个调用栈(call stack)和执行栈(execution stack)。借助调用栈可以跟踪属于同一程序的所有函数(书P88图)。
调用栈的基本单位是帧(frame)。每次函数调用时,都会相应地创建一帧,记录该函数实例在二进制程序中的返回地址(return address),以及局部变量、传入参数等,并将该帧压入调用栈。函数一旦运行完毕,对应的帧随即弹出,运行控制权将被交还给函数的上层调用函数,并按照该帧中记录的返回地址确定在二进制程序中继续执行的位置。
位于调用栈栈底的那帧必然对应于入口主函数main(),若它从调用栈中弹出,则意味着整个程序的运行结束,此后控制权将交还给操作系统。
栈的典型应用
- 栈擅长解决的典型问题具有以下共同特征
- 有明确的算法,但其解答却以线性序列的形式给出
- 无论是递归还是迭代实现,该序列都是依逆序计算输出
* 输入和输出规模不确定,难以事先确定盛放输出数据的容器大小
进制转换
/**
* 十进制转换为任意进制
* */
public void convert(int e, int base) {
final char[] digit = {'0','1','2','3','4','5','6','7','8'
,'9','A','B','C','D','F'};
StackBaseLinkedList<Character> sbll = new StackBaseLinkedList<Character>();
StringBuffer sb = new StringBuffer();
int remainder;
while(e > 0) {
remainder = e % base;
sbll.push(digit[remainder]);
e = e / base;
}
while(!sbll.empty()) {
sb.append(sbll.pop());
}
System.out.print(sb.toString());
}
括号匹配(栈混洗)
当遇到{ [ ( 时,将其压进栈
当遇见} ] ) 时,或栈顶元素不为null并且是与其匹配的左半部分,则将栈顶元素pop。否则括号不匹配。
/**
* 括号匹配,能匹配的括号有()[]{};
* */
public boolean paren(String s) {
StackBaseLinkedList<Character> sbll = new StackBaseLinkedList<Character>();
for(int lo = 0 ; lo < s.length() ; lo++) {
switch(s.charAt(lo)) {
case '(' :
case '[' :
case '{' : sbll.push(s.charAt(lo));
break;
//当存在右括号而栈为空时,直接返回false
case ')' : if(sbll.empty() || '(' != sbll.pop()) return false;
break;
case ']' : if(sbll.empty() || '[' != sbll.pop()) return false;
break;
case '}' : if(sbll.empty() || '{' != sbll.pop()) return false;
break;
}
}
return sbll.empty();
}
中缀表达式求值
将运算表达式的运算符与运算数分别压入两个不同的栈optr与opnd。
根据oprt栈顶元素与当前操作符查表得到优先级次序’<’,’=’,’>’。
运算优先级’<’:当前运算符优先级别更高,不能进行运算。将其压入栈optr
运算优先级’=’:当前运算符优先级相等,只存在于左右括号的匹配和为了方便算法处理引入的左右哨兵匹配。
运算优先级’>’:当前运算符优先级较低,执行栈顶运算符的运算
//运算优先级表
private final char[][] pri = {
/* |----------------当前运算符----------------|*/
/* + - * / ^ ! ( ) \0 */
/*-- +*/ {'>','>','<','<','<','<','<','>','>'},
/*| -*/ {'>','>','<','<','<','<','<','>','>'},
/*栈 **/ {'>','>','>','>','<','<','<','>','>'},
/*顶 /*/ {'>','>','>','>','<','<','<','>','>'},
/*运 ^*/ {'>','>','>','>','>','<','<','>','>'},
/*算 !*/ {'>','>','>','>','>','>',' ','>','>'},
/*符 (*/ {'<','<','<','<','<','<','<','=',' '},
/*| )*/ {' ',' ',' ',' ',' ',' ',' ',' ',' '},
/*-- \0*/ {'<','<','<','<','<','<','<',' ','='}
};
/**
* 中缀表达式求值,并产生逆波兰式RPN
*
*/
public int evaluate(String s,ArrayList<String> RPN) {
//用来存放运算数的栈
StackBaseLinkedList<Integer> opnd = new StackBaseLinkedList<Integer>();
//用来存放运算符的栈
StackBaseLinkedList<Character> optr = new StackBaseLinkedList<Character>();
char op ;
int p0opnd,p1opnd,lo = 0;
//添加右哨兵,方便算法。否则需要额外处理数组脚标越界问题
s = s + "\0";
//添加左哨兵,方便算法
optr.push('\0');
//循环条件有问题,最后一位是运算符号时会少算一次
// for(int lo = 0 ; lo < s.length() ;) {
while(!optr.empty()) {
if(isDigit(s.charAt(lo))) {
lo = readNumber(s,lo,opnd);
append(RPN,opnd.top());
} else {
switch(orderBetween(optr.top(),s.charAt(lo))) {
case '<' : optr.push(s.charAt(lo));
lo++;
break;
//只有左右括号或者表达式结束才会出现'='
case '=' : optr.pop();
lo++;
break;
//栈顶运算符优先于当前运算符,先处理栈顶运算符
case '>' : op = optr.pop();
append(RPN,op);
if(op == '!') {
//一元运算符
p0opnd = opnd.pop();
opnd.push(calcu(op,p0opnd));
} else {
//二元运算符
p0opnd = opnd.pop();
p1opnd = opnd.pop();
opnd.push(calcu(op,p0opnd,p1opnd));
}
break;
default : System.exit(1);
}
}
}
return opnd.pop();
}
private boolean isDigit(char c) {
// (int)'0' = 48 , (int)'9' = 57
if((int)c < 58 && (int)c > 47) {
return true;
} else {
return false;
}
}
private char orderBetween(char op1,char op2) {
return pri[oprtRank(op1)][oprtRank(op2)];
}
private int oprtRank(char op) {
switch(op) {
case '+' : return 0;
case '-' : return 1;
case '*' : return 2;
case '/' : return 3;
case '^' : return 4;
case '!' : return 5;
case '(' : return 6;
case ')' : return 7;
case '\0': return 8;
}
return -1;
}
private int readNumber(String s,int lo,StackBaseLinkedList<Integer> opnd) {
int temp = 0, i = lo;
opnd.push((int)s.charAt(i) - 48);
while((++i < s.length()) && isDigit(s.charAt(i))) {
temp = opnd.pop();
temp = temp * 10 + ((int)s.charAt(i) - 48);
opnd.push(temp);
}
return i;
}
public int calcu(char op,int p0pnd) {
// 0的阶乘等于1
if(p0pnd == 0 ) {
return 1;
}
for(int i = p0pnd - 1 ; i > 0 ; i--) {
p0pnd = p0pnd * i;
}
return p0pnd;
}
private int calcu(char op,int p0opnd,int p1opnd) {
int j = p1opnd;
switch(op) {
case '+' : return p1opnd + p0opnd;
case '-' : return p1opnd - p0opnd;
case '*' : return p1opnd * p0opnd;
case '/' : return p1opnd / p0opnd;
case '^' :
for(int i = 0 ; i < p0opnd - 1 ; i++) {
p1opnd = p1opnd * j;
}
break;
}
return p1opnd;
}
}
private void append(ArrayList<String> RPN,int opnd) {
RPN.add(Integer.toString(opnd));
}
private void append(ArrayList<String> RPN,char op) {
RPN.add(Character.toString(op));
}
逆波兰式求值
/**
* RPN式求值
* 这里RPN表达式不能用StringBuffer,StringBuffer无法分辨个位数与多位数。
* */
public int rpnEvaluation(ArrayList<String> RPN) {
StackBaseLinkedList<Integer> s = new StackBaseLinkedList<Integer>();
int p0opnd,p1opnd,lo = 0;
char op;
while(lo < RPN.size()) {
op = RPN.get(lo).charAt(0);
if(Character.isDigit(op)) {
s.push(Integer.parseInt(RPN.get(lo)));
lo++;
} else {
if(op == '!') {
//一元运算符
p0opnd = s.pop();
s.push(calcu(op,p0opnd));
} else {
//二元运算符
p0opnd = s.pop();
p1opnd = s.pop();
s.push(calcu(op,p0opnd,p1opnd));
}
lo++;
}
}
return s.pop();
}