数据结构之栈

数据结构栈的相关学习:

简介

限定仅在表尾进行插入和删除操作的线性表。允许插入和删除的一端成为栈顶,另一端成为栈低,不含任何元素的栈成为空栈,栈又称为先进先出的线性表,简称LIFO结构。

栈的插入操作,叫做进栈,也称压栈,入栈。

栈的删除操作,也叫出战,也有的叫做弹栈。

入栈

出栈

实现一个栈

栈主要包含两个操作,入栈和出栈,也就是在栈顶插入一个数据和从栈顶删除一个数据。栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。
根据上一节我们实现的数组代码我们来实现一个自己的栈。

1
2
3
4
5
6
7
8
public interface Stack<E> {

int getSize(); //获取栈大小
boolean isEmpty(); //判断是否为空
void push(E e); //压栈
E pop(); //弹栈
E peek(); //查看栈顶
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class ArrayStack<E> implements Stack<E> {

private Array<E> array;

public ArrayStack(int capacity){
array = new Array<>(capacity);
}

public ArrayStack(){
array = new Array<>();
}

@Override
public int getSize(){
return array.getSize();
}

@Override
public boolean isEmpty(){
return array.isEmpty();
}

public int getCapacity(){
return array.getCapacity();
}

@Override
public void push(E e){
array.addLast(e);
}

@Override
public E pop(){
return array.removeLast();
}

@Override
public E peek(){
return array.getLast();
}

@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Stack: ");
res.append('[');
for(int i = 0 ; i < array.getSize() ; i ++){
res.append(array.get(i));
if(i != array.getSize() - 1)
res.append(", ");
}
res.append("] top");
return res.toString();
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {

public static void main(String[] args) {

ArrayStack<Integer> stack = new ArrayStack<>();

for(int i = 0 ; i < 6 ; i ++){
stack.push(i); //压栈
System.out.println(stack);
}

stack.pop(); //弹栈
System.out.println(stack);
System.out.println("栈顶为:"+stack.peek());
}
}

分析

不管是顺序栈还是链式栈,我们存储数据只要一个大小为 n 的数组就够了。入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 O(1)。
这里存储数据需要一个大小为 n 的数组,并不是说空间复杂度就是 O(n)。因为,这 n 个空间 是必须的,无法省掉。所以我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。

出栈的时间复杂度仍然是 O(1)。但是,对于入栈操作来说,情况就不一样。当栈中有空闲空间时,入栈操作的时间复杂度 为 O(1)。但当空间不够时,就需要重新申请内存和数据搬移,所以时间复杂度就变成了 O(n)。

摊还分析

栈空间不够时,我们重新申请一个是原来大小两倍的数组;为了简化分析,假设只有入栈操作没有出栈操作;定义不涉及内存搬移的入栈操作为 simple-push 操作,时间复杂度为 O(1)。如果当前栈大小为 K,并且已满,当再有新的数据要入栈时,就需要重新申请 2 倍大小的内存,并 且做 K 个数据的搬移操作,然后再入栈。但是,接下来的 K-1 次入栈操作,我们都不需要再重新申请内存和搬移数据,所以这 K-1 次入栈操作都只需要一个 simple-push 操作就可以完成。

栈应用

函数应用

操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。

这是一个比较费流量的GIF!!!

表达式求值

我将算术表达式简化为只包含加减乘除四则运算,比如:34+13*9+44-12/3。对于这个四则运算编译器就是通过两个栈来实现的。其中一个保存操作数的栈,另一个是保存运算符的栈。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。
如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算 完的结果压入操作数栈,继续比较。

在括号匹配

我们假设表达式中只包含三种括号,圆括号 ()、方括号 [] 和花括号{},并且它们可以任意嵌套。比如,{[{}]}或 [{()}([])] 等都为合法格式,而{[}()] 或 [({)] 为不合法的格式。在给你一个包含三种括号的表达式字符串。我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比 如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不 能配对的右括号,或者栈中没有数据,则说明为非法格式。当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.Stack;

class Solution {

public boolean isValid(String s) {

Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(' || c == '[' || c == '{')
stack.push(c);
else {
if (stack.isEmpty())
return false;

char topChar = stack.pop();
if (c == ')' && topChar != '(')
return false;
if (c == ']' && topChar != '[')
return false;
if (c == '}' && topChar != '{')
return false;
}
}
return stack.isEmpty();
}


public static void main(String[] args) {

System.out.println((new Solution()).isValid("({[]})[]{}"));
System.out.println((new Solution()).isValid("([)]"));
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值