栈的定义
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
- 将允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom)
- 不含任何数据元素的栈称为空栈
- 栈又称为后进先出(LAST IN FIRST OUT)的线性表,简称LIOF
- 栈本身是一个线性表,其数据元素具有线性关系,它是一种特殊的线性表
- 栈的插入操作,叫作进栈,也称压栈、入栈
- 栈的删除操作,叫作出栈,也称弹栈
栈的接口实现
栈可以顺序储存实现也可以链表储存实现
抽取共性定义将确定栈有如下抽象方法:
- size 容量
- isEmpty 判空
- push 进栈
- pop 出栈
- peek 查看栈顶
- clear 清空栈
package P1.Interface;
import java.util.Iterator;
public interface Stack<E> extends Iterable<E> {
public int size();
public boolean isEmpty();
//入栈 进栈 一个元素 在线性表的表尾添加一个元素
public void push(E element);
//出栈 弹出 一个元素 在线性表的表尾删除一个元素
public E pop();
//查看当前栈顶元素
public E peek();
//清空栈
public void clear();
}
ArrayStack实现
因为栈是一种特殊的线性表,我们完全可以复用之前的线性表ArrayList来实现ArrayStack
实现Stack接口中的方法:
package P2.linearStructure;
import P1.Interface.Stack;
import java.util.Iterator;
public class ArrayStack<E> implements Stack<E> {
private ArrayList<E> list;
public ArrayStack() {
list = new ArrayList<>();
}
public ArrayStack(int capacity) {
list = new ArrayList<>(capacity);
}
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void push(E element) {
list.add(element);
}
@Override
public E pop() {
return list.remove(list.size() - 1);
}
@Override
public E peek() {
return list.get(list.size() - 1);
}
@Override
public void clear() {
list.clear();
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
@Override
public String toString() {
return list.toString();
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (o instanceof ArrayStack) {
ArrayStack other = (ArrayStack) o;
return this.list.equals(other.list);
}
return false;
}
}
注意:栈的增删的时间复杂度是O(1)
栈的理论拓展
栈,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。
首先系统或者数据结构栈中数据内容的读取与插入(压入push和 弹出pop)是两回事!压入是增加数据,弹出是删除数据 ,这些操作只能从栈顶即最低地址作为约束的接口界面入手操作 ,但读取栈中的数据是随便的没有接口约束之说。很多人都误解这个理念从而对栈产生困惑。而系统栈在计算机体系结构中又起到一个跨部件交互的媒介区域的作用 即 cpu 与内存的交流通道 ,cpu只从系统给我们自己编写的应用程序所规定的栈入口线性地读取执行指令, 用一个形象的词来形容它就是pipeline(管道线、流水线)。cpu内部交互具体参见 EU与BIU的概念介绍。
栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。
栈可以用来在函数调用的时候存储断点,做递归时要用到栈!
在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。在i386机器中,栈顶由称为esp的寄存器进行定位。压栈的操作使得栈顶的地址减小,弹出的操作使得栈顶的地址增大。
栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:
- 函数的返回地址和参数
- 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
栈的应用(一)
进制转换器
十进制转十六进制
- 将取结果不断进栈
- 当取整结果为零时,开始弹栈
- 弹栈结果即所求十六位数
代码实践:
package P2.linearStructure;
//十进制转十六进制
public class DecToHex {
public static void main(String[] args) {
int num = 654321;
ArrayStack<String> stack = new ArrayStack<>();
while (num != 0) {
int a = num % 16;
if (a < 10) {
stack.push(a + "");
} else {
//10 - A 11 - B 12 - C 13 - D 14 - E 15 - F
// 65 66 67 68 69 70
stack.push((char)(a + 55) + "");
}
num /= 16;
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()) {
sb.append(stack.pop());
}
System.out.println(sb.toString());
}
}
十六进栈转十进制
- 将输入的数字以字符串接收并进栈
- 依次弹栈并称从0开始的次数
- 汇总相加得到所求十进制数
代码实践:
package P2.linearStructure;
//十六进制 转 十进制
public class HexToDec {
public static void main(String[] args) {
String hex = "9FBF1";
ArrayStack<Character> stack = new ArrayStack<>();
for (int i = 0; i < hex.length(); i++) {
stack.push(hex.charAt(i));
}
int sum = 0;
int mi = 0;
while (!stack.isEmpty()) {
//9 F B F 1
char c = stack.pop(); // '1'
sum += getNumber(c) * Math.pow(16, mi);
mi++;
}
System.out.println(sum);
}
private static int getNumber(char c) {
if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'Z')) {
throw new IllegalArgumentException("Wrong char !");
}
if (c >= '0' && c <= '9') {
return c - '0';
} else {
return c - 'A' + 10;
}
}
}
注意:十进制中 10 11 12 13 14 15 与 十六进制中 A B C D E F 的转换
使用ASCII码,实现数字相加并转码
代码实践:
int a = num % 16;
if (a < 10) {
stack.push(a + "");
} else {
//10 - A 11 - B 12 - C 13 - D 14 - E 15 - F
// 65 66 67 68 69 70
stack.push((char)(a + 55) + "");
}
num /= 16;
栈的应用(二)
回文判断
注意:会出现11223344的bug
代码实践:
package P2.linearStructure;
//判断回文
public class JudgingPalindrome {
public static void main(String[] args) {
solution01();
System.out.println(solution02());
}
private static boolean solution02() {
String text = "上海自来水来自海上";
int i = 0;
int j = text.length() - 1;
while (true) {
if (text.charAt(i) == text.charAt(j)) {
i++;
j--;
} else {
return false;
}
if (j <= i) {
return true;
}
}
}
private static void solution01() {
String text = "上海自来水来自海上";
ArrayStack<Character> stack = new ArrayStack<>();
for (int i = 0; i < text.length(); i++) {
if (text.length() % 2 == 1 && i == text.length() / 2) {
continue;
}
char c = text.charAt(i);
if (stack.isEmpty()) {
stack.push(c);
} else {
if (c != stack.peek()) {
stack.push(c);
} else {
stack.pop();
}
}
}
System.out.println(stack.isEmpty());
}
}
对称括号匹配
代码实践:
package P2.linearStructure;
import java.util.HashMap;
//括号匹配问题
public class ParenthesisMatching {
public static void main(String[] args) {
solution01();
solution02();
}
private static void solution02() {
String str = "{}";
HashMap<Character,Character> map = new HashMap<>();
map.put('[',']');
map.put('<','>');
map.put('(',')');
map.put('{','}');
ArrayStack<Character> stack = new ArrayStack<>();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (stack.isEmpty()) {
stack.push(c);
} else {
char top = stack.peek();
if (map.containsKey(top) && c == map.get(top)) {
stack.pop();
} else {
stack.push(c);
}
}
}
System.out.println(stack.isEmpty());
}
private static void solution01() {
String str = "{()[[()]]<>{}()<>}()";
ArrayStack<Character> stack = new ArrayStack<>();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (stack.isEmpty()) {
stack.push(c);
} else {
char top = stack.peek();
if (top - c == -1 || top - c == -2) {
stack.pop();
} else {
stack.push(c);
}
}
}
System.out.println(stack.isEmpty());
}
}
注意:括号匹配问题使用HashMap会更为高效,所以实践提供了两种方法
栈的应用(三)[重点]
中缀计算器
代码实践:
package P2.linearStructure;
public class InfixCalculator {
public static void main(String[] args) {
String expression = "(10+20/2*3)/2+8";
try {
int result = evaluateExpression(expression);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Wrong expression:" + expression);
}
}
private static int evaluateExpression(String expression) {
//需要两个辅助栈
ArrayStack<Character> operatorStack = new ArrayStack<>();
ArrayStack<Integer> numberStack = new ArrayStack<>();
//格式化表达式
expression = insertBlanks(expression);
String[] tokens = expression.split(" ");
for (String token : tokens) { //token == tokens
//过滤空串
if (token.length() == 0) {
continue;
//遍历到+ - 号
} else if (token.equals("+") || token.equals("-")) {
while (!operatorStack.isEmpty() && (operatorStack.peek() == '+' || operatorStack.peek() == '-' || operatorStack.peek() == '*' || operatorStack.peek() == '/')) {
//如果之前是+-*/ 则需要弹栈并进行计算
processAnOperator(numberStack, operatorStack);
}
//如果操作符栈为空 或者 不为空 但是栈顶是(
operatorStack.push(token.charAt(0));
//如果遍历到*/
}else if (token.equals("*") || token.equals("/")) {
while (!operatorStack.isEmpty() && (operatorStack.peek() == '*' || operatorStack.peek() == '/')) {
//如果之前是别的 */ 则需要弹栈并进行计算
processAnOperator(numberStack, operatorStack);
}
//如果操作符栈为空 或者 不为空 但是栈顶是(
operatorStack.push(token.charAt(0));
//遍历到( 则直接进栈
}else if (token.equals("(")) {
operatorStack.push(token.charAt(0));
//遍历到) 则需要完成从 ( 到 ) 之间的所有运算
}else if (token.equals(")")) {
//只要操作符栈的栈顶不是左括号 ( 就依次进行弹栈 并计算结果
while (operatorStack.peek() != '(') {
processAnOperator(numberStack, operatorStack);
}
//清理左括号
operatorStack.pop();
//遍历到数字
} else {
numberStack.push(new Integer(token));
}
}
//处理最后的操作符
while (!operatorStack.isEmpty()) {
processAnOperator(numberStack, operatorStack);
}
return numberStack.pop();
}
//操作符栈弹出一个元素 数字栈弹出两个元素 并进行计算 获得新数字 进入数字栈
private static void processAnOperator(ArrayStack<Integer> numberStack, ArrayStack<Character> operatorStack) {
char op = operatorStack.pop();
int num1 = numberStack.pop();
int num2 = numberStack.pop();
// num2 op num1
if (op == '+') {
numberStack.push(num2 + num1);
} else if (op == '-') {
numberStack.push(num2 - num1);
} else if (op == '*') {
numberStack.push(num2 * num1);
} else {
numberStack.push(num2 / num1);
}
}
//对原表达式进行格式化处理 在+-*/() 的两边同时加上' '空格 使其能被split方法直接操作
private static String insertBlanks(String expression) {
/*
( 10 + 20 / 2 * 3 ) / 2 + 8
*/
StringBuilder sb = new StringBuilder();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' ) {
sb.append(' ');
sb.append(c);
sb.append(' ');
} else {
sb.append(c);
}
}
return sb.toString();
}
}
栈的应用(四)[重点]
中缀转后缀
后缀表达式
栈实现中缀转后缀过程
栈实现后缀表达式过程
代码实践:
中缀转后缀:
package P2.linearStructure;
public class InfixToSuffix {
public static void main(String[] args) {
String expression = "(10+20/2*3)/2+8";
String suffixExpression = infixToSuffix(expression);
System.out.println(suffixExpression);
}
public static String infixToSuffix(String expression) {
//操作符栈
ArrayStack<String> opStack = new ArrayStack<>();
//后缀表达的线性表
ArrayList<String> suffixList = new ArrayList<>();
//格式化字符串
expression = insertBlanks(expression);
String[] tokens = expression.split(" ");
for (String token : tokens) {
if (token.length() == 0) {
continue;
}
//判断操作符+-*/
if (isOperator(token)) {
/*
什么时候操作符进栈?
1、栈为空
2、栈顶是(
3、栈顶是操作符 且优先级比当前token小
什么时候需要出栈?
1、栈顶操作符的优先级 >= 当前token
2、
*/
while (true) {
if (opStack.isEmpty() || opStack.peek().equals("(") || priority(opStack.peek()) < priority(token)) {
opStack.push(token);
break;
}
suffixList.add(opStack.pop());
}
} else if (token.equals("(")) {
opStack.push(token);
} else if (token.equals(")")) {
while (!opStack.peek().equals("(")) {
suffixList.add(opStack.pop());
}
opStack.pop();
} else if (isNumber(token)) {
suffixList.add(token);
} else {
throw new IllegalArgumentException("Wrong char!" + expression);
}
}
while (!opStack.isEmpty()) {
suffixList.add(opStack.pop());
}
//将数字元素和操作符元素进行拼接
StringBuilder sb = new StringBuilder();
for (int i = 0; i< suffixList.size(); i ++) {
sb.append(suffixList.get(i));
sb.append(' ');
}
return sb.toString();
}
private static int priority(String token) {
if (token.equals("+") || token.equals("-")) {
return 0;
}
if (token.equals("*") || token.equals("/")) {
return 1;
}
return -1;
}
private static boolean isNumber(String token) {
return token.matches("\\d+");
}
private static boolean isOperator(String token) {
return token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/");
}
private static String insertBlanks(String expression) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' ) {
sb.append(' ');
sb.append(c);
sb.append(' ');
} else {
sb.append(c);
}
}
return sb.toString();
}
}
后缀计算器:
package P2.linearStructure;
import javax.swing.*;
public class SuffixCalculator {
public static void main(String[] args) {
String expression = "(10+20/2*3)/2+8";
String suffixExpression = InfixToSuffix.infixToSuffix(expression);
int result = evaluateSuffixExpression(suffixExpression);
System.out.println(result);
}
private static int evaluateSuffixExpression(String expression) {
ArrayStack<Integer> stack = new ArrayStack<>();
String[] tokens = expression.split(" ");
for (String token : tokens) {
if (token.length() == 0) {
continue;
}
if (isNumber(token)) {
stack.push(new Integer(token));
} else {
processAnOperator(stack, token);
}
}
return stack.pop();
}
private static void processAnOperator(ArrayStack<Integer> stack, String token) {
int num1 = stack.pop();
int num2 = stack.pop();
if (token.equals("+")) {
stack.push(num2 + num1);
} else if (token.equals("-")) {
stack.push(num2 - num1);
} else if (token.equals("*")) {
stack.push(num2 * num1);
} else if (token.equals("/")) {
stack.push(num2 / num1);
}
}
private static boolean isNumber(String token) {
return token.matches("\\d+");
}
}
栈的优化
双端栈的实现
双端栈的定义:
将一个线性表的两端当做栈底分别进行入栈和出栈操作。主要利用了栈“栈底位置不变,而栈顶位置动态变化”的特性。
双端栈是线性表的一种,更是栈的一个特殊分类,所以我们任可以用动态数组和栈的思想来实现双端栈,但由于其操作过于特殊,并不能借助ArrayList或ArrayStack
双端栈扩容问题
代码实践:
package P2.linearStructure;
import java.util.Iterator;
//双端栈
public class ArrayDoubleEndStack<E> implements Iterable<E> {
//左端栈的栈顶
private int ltop;
//右端栈的栈顶
private int rtop;
//存储数据的容器
private E[] data;
//数组容器的默认容量
private static int DEFAULT_CAPACITY = 10;
public ArrayDoubleEndStack() {
data = (E[]) new Object[DEFAULT_CAPACITY];
ltop = -1;
rtop = data.length;
}
public void pushLeft(E element) {
//判断双端栈是否满栈
if (ltop + 1 == rtop) {
resize(data.length * 2);
}
data[++ltop] = element;
}
public void pushRight(E element) {
if (ltop + 1 == rtop) {
resize(data.length * 2);
}
data[--rtop] = element;
}
private void resize(int newLen) {
/*
关于双端栈的 扩容 和 缩容
遍历左边索引不变
遍历右边索引增加 扩容个长度
*/
E[] newData = (E[]) new Object[newLen];
//复制左端栈元素
for (int i = 0; i <= ltop; i++) {
newData[i] = data[i];
}
//复制右端栈元素
int index = rtop;
for (int i = newLen - sizeRight(); i < newLen; i++) {
newData[i] = data[index++];
}
rtop = newLen - sizeRight();
data = newData;
}
public E popLeft() {
if (isLeftEmpty()) {
throw new IllegalArgumentException("left stack is null");
}
E ret = data[ltop--];
if (sizeLeft() + sizeRight() <= data.length / 4 && data.length > DEFAULT_CAPACITY) {
resize(data.length / 2);
}
return ret;
}
public E popRight() {
if (isRightEmpty()) {
throw new IllegalArgumentException("right stack is null");
}
E ret = data[rtop++];
if (sizeLeft() + sizeRight() <= data.length / 4 && data.length > DEFAULT_CAPACITY) {
resize(data.length / 2);
}
return ret;
}
public E peekLeft() {
if (isLeftEmpty()) {
throw new IllegalArgumentException("left stack is null");
}
return data[ltop];
}
public E peekRight() {
if (isRightEmpty()) {
throw new IllegalArgumentException("right stack is null");
}
return data[rtop];
}
public boolean isLeftEmpty() {
return ltop == -1;
}
public boolean isRightEmpty() {
return rtop == data.length;
}
public int sizeLeft() {
return ltop + 1;
}
public int sizeRight() {
return data.length - rtop;
}
@Override
public String toString() {
//1 2 3 7 8 9
StringBuilder sb = new StringBuilder();
sb.append('[');
if (isLeftEmpty() && isRightEmpty()) {
sb.append(']');
return sb.toString();
}
//先解决左边
for (int i = 0; i <= ltop; i++) {
sb.append(data[i]);
if (i == ltop && isRightEmpty()) {
sb.append(']');
} else {
sb.append(',');
}
}
//再解决右边
for (int i = rtop; i < data.length; i++) {
sb.append(data[i]);
if (i == data.length - 1) {
sb.append(']');
} else {
sb.append(',');
}
}
return sb.toString();
}
@Override
public Iterator<E> iterator() {
return new ArrayDoubleEndStackIterator();
}
class ArrayDoubleEndStackIterator implements Iterator<E> {
private ArrayList<E> list;
private Iterator<E> it;
public ArrayDoubleEndStackIterator() {
list = new ArrayList<>();
for (int i = 0; i <= ltop; i++) {
list.add(data[i]);
}
for (int i = rtop; i < data.length; i++) {
list.add(data[i]);
}
it = list.iterator();
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public E next() {
return it.next();
}
}
}