什么是栈?
如何理解栈
生活中的例子比喻:比如我们在放盘子的时候都是从下往上一个个放,拿的时候是从上往下一个个的那,不能从中间抽,这种其实就是一个典型的栈型数据结构。
特性:后进先出即Last In First Out (LIFO)
栈如何实现
- 其实它是一个限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。
- 向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素
- 从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈的分类
- 基于数组的栈——以数组为底层数据结构时,通常以数组头为栈底,数组头到数组尾为栈顶的生长方向
- 基于单链表的栈——以链表为底层的数据结构时,以链表头为栈顶(链表知道head),便于节点的插入与删除,压栈产生的新节点将一直出现在链表的头部
区别:最大的区别就是扩容,链表天然支持动态扩容。栈溢出。
栈的基本操作
假定以数组来实现
- 入栈
- 出栈
- 时间复杂度分析
栈的实际应用
- 括号匹配
- 函数嵌套调用
void main{ add(); }
void add(){ sub(); }
// 是不是这个sub最先完成。sub又是最后才进来的吧 是不是就是后进先出,函数调用就是用的栈来实现的
- 数学表达式求值
编程题
通过数组实现一个栈
package stack;
import java.util.Arrays;
/**
* 数组实现栈:操作核心在栈顶
* 头为栈底
* 尾为栈头
*
* @author zw
* @create 2023-03-25 2:45
*/
public class ArrayStack<Item> implements MyStack<Item> {
//最好就是开始的时候就设置大小
private Item[] elementData = (Item[]) new Object[1];
//大小 初始的元素个数
private int size = 0;
// 扩容因子
private final double DILATANCY_FACTOR = 0.75d;
/**
* 入栈 O(1)
* @param item
*/
@Override
public void push(Item item) {
elementData[size++] = item;
resize();
}
/**
* 出栈 O(1)
* @return
*/
@Override
public Item pop() {
if (isEmpty()) {
return null;
}
// 删除数组一个个元素,后边元素前移
Item item = elementData[--size];
// 回收删除的数据
elementData[size] = null;
resize();
return item;
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
/**
* 扩容O(n)
*/
private void resize() {
// 扩容
if (size >= elementData.length * DILATANCY_FACTOR) {
elementData = Arrays.copyOf(elementData, size << 1);
}
//缩容
if (size < elementData.length * (1 - DILATANCY_FACTOR)) {
elementData = Arrays.copyOf(elementData, size+1);
}
}
private void print() {
StringBuffer sb = new StringBuffer();
if (size == 0) {
sb.append("[]");
} else {
sb.append("size=").append(size).append(" [");
for (int i = 0; i < size; i++) {
sb.append(elementData[i]).append(",");
}
sb.append("]");
}
System.out.print(sb.toString().replaceAll(",]", "]"));
}
}
测试用例
public static void main(String[] args) {
ArrayStack<Integer> stack = new ArrayStack<>();
for (int i = 0; i < 5; i++) {
stack.push(i);
System.out.print("入栈:"+i+" ");
stack.print();
System.out.println();
}
for (int i = 0; i < 5; i++) {
Integer pop = stack.pop();
System.out.print("出栈: "+ pop +" ");
stack.print();
System.out.println();
}
}
运行结果
入栈:0 size=1 [0]
入栈:1 size=2 [0,1]
入栈:2 size=3 [0,1,2]
入栈:3 size=4 [0,1,2,3]
入栈:4 size=5 [0,1,2,3,4]
出栈: 4 size=4 [0,1,2,3]
出栈: 3 size=3 [0,1,2]
出栈: 2 size=2 [0,1]
出栈: 1 size=1 [0]
出栈: 0 []
通过链表实现一个栈
/**
* 单向链表实现栈:操作核心在栈顶
* 链表头节点为栈顶
*
* @author zw
* @create 2023-03-25 2:45
*/
public class LinkedListStack<Item> implements MyStack<Item> {
MyLinkedList<Item> linkedList = new MyLinkedList<>();
/**
* 入栈 O(1)
* @param item
*/
@Override
public void push(Item item) {
linkedList.addHead(item);
}
/**
* 出栈 O(1)
* @return
*/
@Override
public Item pop() {
Item first = linkedList.getFirst();
linkedList.removeHead();
return first;
}
@Override
public int size() {
return linkedList.size;
}
@Override
public boolean isEmpty() {
return linkedList.size == 0;
}
}
测试用例
public static void main(String[] args) {
LinkedListStack<Integer> stack = new LinkedListStack<>();
for (int i = 0; i < 5; i++) {
stack.push(i);
System.out.print("入栈:"+i+" ");
stack.linkedList.print();
}
for (int i = 0; i < 5; i++) {
Integer pop = stack.pop();
System.out.print("出栈: "+ pop +" ");
stack.linkedList.print();
}
}
运行结果
入栈:0 size=1 [0]
入栈:1 size=2 [1,0]
入栈:2 size=3 [2,1,0]
入栈:3 size=4 [3,2,1,0]
入栈:4 size=5 [4,3,2,1,0]
出栈: 4 size=4 [3,2,1,0]
出栈: 3 size=3 [2,1,0]
出栈: 2 size=2 [1,0]
出栈: 1 size=1 [0]
出栈: 0 []
括号匹配
如何设计一个括号匹配的功能?比如给你一串括号让你判断是否符合我们的括号原则,如下所示:
该算法的应用,如判断占位符表达式是否正确 #{age}
[(){()}{}]符合
{}[}}{}}]]] 不符合
思路:左右括号建立匹配关系。遍历字符串字符,遇到左括号压栈,遇到右括号弹栈,遍历完后栈中没数据则匹配成功
package stack;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* 括号匹配:
*
* @author zw
* @create 2023-03-25 4:13
*/
public class ParenthesisMatch {
//定义左右括号的对应关系
static Map<Character, Character> bracket = new HashMap<>();
static {
bracket.put('(', ')');
bracket.put('[', ']');
bracket.put('{', '}');
}
public static boolean isOk(String s) { //s表示的就是待匹配的括号串 [}使用字符来表示 时间复杂度 O(n)
MyStack<Character> stack = new ArrayStack<Character>();
for (char c : s.toCharArray()) {
Character character = bracket.get(c);
// 匹配上则为左括号
if (character != null) {
stack.push(c);
}
// 匹配右括号
else {
stack.pop();
}
}
return stack.isEmpty();
}
}
测试用例
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String s = scanner.next();
System.out.println("s的匹配结果:" + isOk(s));
}
}
运行结果
[(){()}()]
s的匹配结果:true
[(){()}{}]
s的匹配结果:true
{}[}}{}}]]]
s的匹配结果:false
(***)-[{-------}]
s的匹配结果:true
如何设计一个浏览器的前进和后退功能?
两个栈
数学表达式求值
比如用栈实现一个简单的四则运算:3+11*2+8-15/5,用栈来实现这个算术表达式
两个栈来实现:一个放数字 一个放符号。
我们从头开始遍历这个算术表达式:
1.遇到是数字 我们就直接入栈到数字栈里面去。
2.遇到是符合 就把符号栈的栈顶拿出来做比较。如果说他比栈顶符号的优先级高就直接入栈,如果比符号栈顶的优先级低或者相同,就从符号栈里面取栈顶进行计算(从数字栈中取栈顶的2个数),计算完的结果还要再放入到数字栈中。