重温:数据结构与算法 - 开篇
重温:数据结构与算法 - 复杂度分析(一)
重温:数据结构与算法 - 复杂度分析(二)
重温:数据结构与算法 - 数组
重温:数据结构与算法 - 链表(一)
重温:数据结构与算法 - 链表(二)
叨叨两句
说好的尽量日更o(╥﹏╥)o~,太懒了,最后两个练习题一直没敲,拖了两天才更,啪 啪~ 打脸!
什么是栈
本章介绍一种“操作受限”的数据结构,它仅支持:
- 添加操作(入栈 - push)
- 删除操作(出栈 - pop)
并具备“先进后出,后进先出”特点:
子弹匣.png图中,正在装填的弹匣就是典型的栈结构,先装入的子弹被压入了底部,后装入的在顶部,当使用时优先使用顶部子弹。那么,在内存中给予这种模式存储数据的容器,称之为:栈。
内存结构图.png栈的实现
根据定义,很容抽象出一个只能新增、删除的操作接口来:
public interface Stack<T> {
//压栈
boolean push(T item);
//弹栈
T pop();
// 返回栈顶数据,不弹栈
T peek();
//是否为空
boolean isEmpty();
//栈中数据的数量
int size();
//清空
void clear();
}
有了操作标准,拿什么来存储实际数据?
- 数组 ?
- 链表 ?
细想一下,无论是使用数组还是链表存储数据,只要出栈、入栈规则满足“先进后出、后进先出”,那它就是我们想要的栈型结构,当然不同数据结构实现的栈,底层肯定还是具备其本身的数据结构特性的,例如数组实现的栈型结构内存是连续的,链表则是不连续的,我们把数组实现的栈,称之:顺序栈 ; 链表实现则为:链式栈。
下面以数组为例,实现一个栈容器:
public class ArrayStack<T> implements Stack<T> {
private static final int DEFAULT_SIZE = 10;
// 栈中数据数量
private int count;
// 栈中数据(数组实现)
private Object[] items;
private Object curItem;
// 当前栈能存储的最大容量
private int maxSize;
public ArrayStack() {
this(DEFAULT_SIZE);
}
public ArrayStack(int initSize) {
this.items = new Object[initSize];
this.maxSize = initSize;
}
@Override
public boolean push(T item) {
// 入栈
if (count == maxSize) {
//扩容
grow();
}
items[count] = item;
++count;
curItem = item;
return true;
}
private void grow() {
Object[] temp = items;
items = new Object[temp.length * 2];
for (int i = 0; i < temp.length; i++) {
items[i] = temp[i];
}
maxSize = items.length;
}
@Override
public T pop() {
// 出栈
if (count == 0) return null;
T item = (T) items[count - 1];
items[count - 1] = null;
--count;
if (count == 0) {
curItem = null;
} else {
curItem = items[count - 1];
}
return item;
}
@Override
public T peek() {
return (T) curItem;
}
@Override
public boolean isEmpty() {
return count == 0;
}
@Override
public int size() {
return count;
}
@Override
public void clear() {
if (isEmpty()) return;
for (int i = 0; i < size(); i++) {
items[i] = null;
}
count = 0;
}
public void print() {
for (int i = 0; i < count; i++) {
System.out.print(items[i] + "\t");
}
System.out.println();
}
}
测试:
ArrayStack<String> stack = new ArrayStack<>();
stack.push("1");
stack.push("2");
stack.push("3");
stack.push("4");
stack.push("5");
stack.print();
String item1 = stack.pop();
System.out.println(item1);
String item2 = stack.pop();
System.out.println(item2);
stack.print();
输出:
1 2 3 4 5
5
4
1 2 3
-
时间复杂度:
-
最好时间复杂度:空间够用,直接入栈,复杂度 O(1)
-
最坏时间复杂度:空间不足,动态扩容,复杂度 O(n)
-
平均时间复杂度:还记得复杂度分析(二)中提到的平摊分析法吗?随着数据的改变,算法出现某种固定规律,这里明显当执行完n次O(1)插入操作后,都需要进行一次O(n)的数据位移,正好可以把这n次搬运平摊给前面各自1次的插入操作,得到平摊时间复杂度:O(1)
-
-
空间复杂度:
注意,这里存储数据需要一个大小为 n 的数组,并不是说空间复杂度就是 O(n)。因为,这 n 个空间是必须的,无法省掉。我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。所以栈的插入操作空间复杂度是:O(1)
栈 - 练习1:运算表达式
要求给出一个运算表达式字符串,计算出表达式结果。
输入字符串: 3 + 2 * 5 - 4 / 2
输出 :11
思路:
- 1、利用两个栈,一个用来保存数字,另一个用来保存运算符,从左向右遍历表达式;
- 2、当遇到数字,我们就直接压入数字栈;
- 3、当遇到运算符,就与运算符栈的栈顶元素进行比较,若比运算符栈顶元素优先级高,就将当前运算符压入栈;
- 4、若比运算符栈顶元素的优先级低或者相同,从运算符栈中取出栈顶运算符,从操作数栈顶取出2个操作数,然后进行计算,把计算完的结果压入数字栈;
- 5、重复第3步的操作,继续比较栈顶操作符。
private static void computeExp(String exp) {
ArrayStack<String> numStack = new ArrayStack<>();
ArrayStack<String> opStack = new ArrayStack<>();
char[] chars = exp.toCharArray();
String preItem = null;
for (int i = 0; i < chars.length; i++) {
String item = String.valueOf(chars[i]);
if (isOp(item)) {
// 操作符
while (true) {
if (opStack.isEmpty()) {
// 直接入栈
opStack.push(item);
break;
}
// 比较优先级
String preOp = opStack.pop();
if (opLv(item) > opLv(preOp)) {
// 大于
opStack.push(preOp);
opStack.push(item);
break;
} else {
// 小于等于 , 取出2个数,和栈顶操作符进行运算
String num1 = numStack.pop();
String num2 = numStack.pop();
int result = compute(num2, num1, preOp);
numStack.push(String.valueOf(result));
}
}
} else {
// 数字
if (!numStack.isEmpty() && !isOp(preItem)) {
// 前一个也是数字,拼接后入数字栈
preItem = numStack.pop();
item = preItem + item;
}
// 入数字栈
numStack.push(item);
}
preItem = item;
}
// 清空栈,计算最终结果
int len = opStack.size();
for(int i = 0 ; i < len ; i++){
String num1 = numStack.pop();
String num2 = numStack.pop();
String op = opStack.pop();
int result = compute(num2 , num1 , op);
numStack.push(String.valueOf(result));
}
System.out.println( exp + " = " + numStack.pop());
}
private static int compute(String num2, String num1, String op) {
int n2 = Integer.parseInt(num2);
int n1 = Integer.parseInt(num1);
switch (op) {
case "+":
return n2 + n1;
case "-":
return n2 - n1;
case "*":
return n2 * n1;
case "/":
return n2 / n1;
default:
throw new RuntimeException("no op");
}
}
private static boolean isOp(String item) {
if (item == null) return false;
return item.matches("[+\\-*/]");
}
private static int opLv(String op) {
switch (op) {
case "+":
case "-":
return 1;
case "*":
case "/":
return 2;
default:
throw new RuntimeException("no op");
}
}
测试:
computeExp("3+10*5-8/2");
computeExp("4+8*5/4-7");
computeExp("50/2-3*7+1");
输出:
3+10*5-8/2 = 49
4+8*5/4-7 = 7
50/2-3*7+1 = 5
时间复杂度:O(n²)
空间复杂度:O(1)
栈 - 练习2:模拟浏览器前进后退功能
思路:
利用两个栈,X栈用于存储新打开的页面,Y栈存储后退页面
当点击后退时,将X出栈一个页面,并存入Y栈
当点击前进时,将Y出栈一个页面,并存入X栈
当打开新页面时,入X栈,清空Y栈
private static void go(ArrayStack<String> goStack, ArrayStack<String> backStack) {
// 当点击前进时,将Y出栈一个页面,并存入X栈
if(backStack.isEmpty()) {
System.out.println("go:" + goStack.peek());
return;
}
String page = backStack.pop();
goStack.push(page);
System.out.println("go:" + goStack.peek());
}
private static void back(ArrayStack<String> goStack, ArrayStack<String> backStack) {
// 当点击后退时,将X出栈一个页面,并存入Y栈
String page = goStack.pop();
backStack.push(page);
System.out.println("back:" + goStack.peek());
}
private static void open(String page, ArrayStack<String> goStack, ArrayStack<String> backStack) {
// 当打开新页面时,入X栈,清空Y栈
goStack.push(page);
backStack.clear();
System.out.println("open:" + goStack.peek());
}
测试:
ArrayStack<String> goStack = new ArrayStack<>();
ArrayStack<String> backStack = new ArrayStack<>();
open("页面1", goStack, backStack);
open("页面2", goStack, backStack);
open("页面3", goStack, backStack);
open("页面4", goStack, backStack);
open("页面5", goStack, backStack);
back(goStack, backStack);
back(goStack , backStack);
go(goStack, backStack);
go(goStack, backStack);
go(goStack, backStack);
输出:
open:页面1
open:页面2
open:页面3
open:页面4
open:页面5
back:页面4
back:页面3
go:页面4
go:页面5
go:页面5
时间复杂度:O(1)
空间复杂度:O(1)
网友评论