基础数据结构-栈
面试经典
- 如何设计一个括号匹配的功能?比如给你一串括号让你判断是否符合我们的括号原则,如下所示:[(){()}{}]符合 {}[]{[][[][][]{{}[}}{}}]]] 不符合栈
- 如何设计一个浏览器的前进和后退功能?两个栈
栈的定义和特点
-
栈是一种特殊的线性表,只能在表尾进行插入和删除操作,具有后进先出(LIFO)的特点。
-
栈的应用场景很多,如括号匹配、浏览器的前进和后退功能等。
-
栈可以用数组或链表实现,数组实现的栈更常用,因为它在内存访问上更快,且扩容相对简单。
-
基于数组的栈——以数组为底层数据结构时,通常以数组头为栈底,数组头到数组尾为栈顶的生长方向
-
基于单链表的栈——以链表为底层的数据结构时,以链表头为栈顶,便于节点的插入与删除,压栈产生的新节点将一直出现在链表的头部
-
最大的区别就是扩容,链表天然支持动态扩容。栈溢出。
-
栈的操作和实现
- 栈的基本操作包括入栈(push)、出栈(pop)、获取栈顶元素(peek)、判断栈是否为空(isEmpty)等。
- 用数组实现栈时,需要注意扩容和缩容的问题,以避免空间浪费。
- 代码实现了栈的基本操作,并通过括号匹配的例子展示了栈的应用。
数组实现栈
/**
* 使用数组实现栈
* 1.数组
* 2.头指针:对应栈顶
*/
public class MyArrayStack<T> {
private static final int DEFAULT_SIZE = 10;
private T[] stack;
private int top;
public MyArrayStack() {
stack = (T[]) new Object[DEFAULT_SIZE];
top = -1;
}
public void push(T data){
if (top == stack.length - 1){
resize();
}
stack[++top] = data;
}
public T pop(){
if (top == -1){
return null;
}
T t = stack[top];
stack[top] = null;
top--;
return t;
}
public void resize() {
T[] newStack = (T[]) new Object[stack.length * 2];
for (int i = 0; i < stack.length; i++) {
newStack[i] = stack[i];
}
}
public void print(){
if (top == -1) {
System.out.println("stack is empty");
return;
}
for (int i = top; i >= 0; i--) {
System.out.print(stack[i] + " ");
}
System.out.println();
}
public boolean isEmpty(){
return top == -1;
}
public static void main(String[] args) {
MyArrayStack<Integer> stack = new MyArrayStack<>();
stack.push(1);
stack.push(2);
stack.push(3);
stack.print();
stack.pop();
stack.print();
stack.pop();
stack.print();
}
}
单链表实现栈
/**
* 使用单链表实现栈
* 1.链表节点类
* 2.栈类
*/
public class MyLinkStack {
MyStackNode head;
public MyLinkStack()
{
head = null;
}
public void push(int data)
{
MyStackNode node = new MyStackNode(data);
node.next = head;
head = node;
}
public MyStackNode pop(){
MyStackNode node = head;
if (node != null){
head = node.next;
node.next = null;
}
return node;
}
public void print(){
MyStackNode node = head;
while (node != null){
System.out.print(node.data + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
MyLinkStack stack = new MyLinkStack();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.print("初始栈:");
stack.print();
stack.pop();
System.out.print("出栈:");
stack.print();
stack.pop();
System.out.print("出栈:");
stack.print();
}
}
class MyStackNode{
int data;
MyStackNode next;
public MyStackNode(int data) {
this.data = data;
}
}
栈的时间复杂度
- 栈的入栈和出栈操作的时间复杂度都是O(1),非常高效。
- 括号匹配算法的时间复杂度也是O(n),其中n是括号的数量。
栈的应用
- 函数调用中使用栈来保存函数的调用信息,实现后进先出的调用顺序。
- 用栈实现简单的计算器功能,通过两个栈分别存储数字和符号,按照运算规则进行计算。
- 用栈实现浏览器的前进和后退功能,通过两个栈分别存储访问的页面信息,实现页面的切换。
括号匹配
/**
* 括号匹配问题
* 思路:1.使用栈,遍历字符串,遇到左括号入栈,遇到右括号出栈,如果栈为空,则说明匹配,否则不匹配
*/
public class BracketsProblem {
public static void main(String[] args) {
String str = "()()()()())";
System.out.println(isMatch(str));
}
private static boolean isMatch(String str) {
MyArrayStack<String> stack = new MyArrayStack<String>();
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '(') {
stack.push(str.charAt(i) + "");
} else if (str.charAt(i) == ')') {
String pop = stack.pop();
if (pop == null) {
return false;
}
}
}
return stack.isEmpty();
}
}
数学表达式求值
比如用栈实现一个简单的四则运算:3+11*2+8-15/5,用栈来实现这个算术表达式
两个栈来实现:一个放数字 一个放符号。
我们从头开始遍历这个算术表达式:
1.遇到是数字 我们就直接入栈到数字栈里面去。
2.遇到是符合 就把符号栈的栈顶拿出来做比较。如果说他比栈顶符号的优先级高就直接入栈,如果比符号栈顶的优先级低或者相同,就从符号栈里面取栈顶进行计算(从数字栈中取栈顶的2个数),计算完的结果还要再放入到数字栈中。
/**
* 表达式问题:比如用栈实现一个简单的四则运算:3+11*2+8-15/5
* 注意:需要考虑 * 和 / 的优先级问题
*/
public class ExpressionProblem {
public static void main(String[] args)
{
String expression = "3 + 11 * 2 + 8 - 15 / 5";
int result = calculator(expression);
System.out.println(result);
}
private static int calculator(String expression) {
MyArrayStack<String> numStack = new MyArrayStack<>();
MyArrayStack<String> operatorStack = new MyArrayStack<>();
String[] split = expression.split(" ");
for (int i = 0; i < split.length; i++) {
String c = split[i];
if (isOperator(c)) {
if (isPriority(c)){ // 遇到 * 和 / ,先弹出数据进行处理,将处理后的结果压入栈中
String num1 = numStack.pop();
String num2 = split[++i];
numStack.push(String.valueOf(calculate(num1, num2, c)));
}else{
operatorStack.push(c);
}
}else{
numStack.push(c);
}
}
while (!operatorStack.isEmpty()){
String num1 = numStack.pop();
String num2 = numStack.pop();
String operator = operatorStack.pop();
numStack.push(String.valueOf(calculate(num2, num1, operator)));
}
return Integer.parseInt(numStack.pop());
}
private static boolean isOperator(String c) {
return "+".equals(c) || "-".equals(c) || "*".equals(c) || "/".equals(c);
}
private static boolean isPriority(String c) {
return "*".equals(c) || "/".equals(c);
}
private static int calculate(String num1, String num2, String operator) {
if ("+".equals(operator)){
return Integer.parseInt(num1) + Integer.parseInt(num2);
} else if ("-".equals(operator)) {
return Integer.parseInt(num1) - Integer.parseInt(num2);
} else if ("*".equals(operator)) {
return Integer.parseInt(num1) * Integer.parseInt(num2);
} else if ("/".equals(operator)) {
return Integer.parseInt(num1) / Integer.parseInt(num2);
}
throw new RuntimeException("运算符错误");
}
}
课程总结
- 栈是一种非常重要的数据结构,具有后进先出的特点,在很多场景中都有应用。
- 用数组实现栈时,需要注意扩容和缩容的问题,以提高空间利用率。
- 栈的时间复杂度很低,非常高效,可以用于解决很多实际问题。
- 下节课将讲队列,它是另一种重要的数据结构,与栈的特点和应用场景不同。