3-1 栈和栈的应用:撤销操作和系统栈
- 栈也是一种线性结构
- 相比数组,栈对应的操作时数组的子集
- 只能从一端添加元素,也只能从一端取出元素,这一端叫栈顶
- 后进先出,LIFO
栈的应用
- Undo操作(撤销)
- 程序调用的系统栈:因为有系统栈记录每一次调用过程中的调用点,在编程时进行子过程调用的时候,当一个子过程执行完成之后,可以回到上层调用中断的位置继续执行下去。
- 括号匹配-编译器
3-2 栈的基本实现
public interface Stack<E>{
int getSize();
boolean isEmpty();
void push(E e);
E pop();
E peek();
}
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();
}
}
测试
public class Main {
public static void main(String[] args) {
ArrayStack<Integer> stack = new ArrayStack<>();
for(int i=0;i<5;i++){
stack.push(i);
System.out.println(stack);
}
stack.pop();
System.out.println(stack);
}
}
3-3 栈的另一个应用:括号匹配
leetcode20-Valid Parentheses
维护一个栈,对于输入的括号序列,遇到左括号入栈, 遇到右括号和当前栈顶的左括号匹配,直到栈为空,匹配失败则括号不匹配
import java.util.Stack;
public 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.empty())
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("()[]{[]}")));
}
}
3-4 关于leetcode的说明
- 如果使用我们自己定义的栈类
ArrayStack<Character> stack = new Stack<>();
- 利用相关话题,从简单到难做,可以看一看熟悉题目
- 避免学习中的“完美主义”,掌握好“度”,60分也是收获,20分也是收获,100分也是收获。
- 学习本着自己的目标去
- 该课程首要目标是了解各个数据结构中得底层实现原理
3-5 数组队列
队列Queue
- 队列也是一种线性结构
- 相比数组,队列对应的操作是数组的子集。
- 只能从一端(队尾)添加元素,只能从另一端(队首)取出元素
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array;
public ArrayQueue(int capacity) {
array = new Array<>(capacity);
}
public ArrayQueue() {
array = new Array<>();
}
@Override
public int getSize(){
return array.getSize();
}
@Override
public boolean isEmpty(){
return array.isEmpty();
}
@Override
public void enqueue(E e){
array.addLast(e);
}
@Override
public E dequeue(){
return array.removeFirst();
}
@Override
public E getFront(){
return array.getFirst();
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Queue: ");
res.append("front [");
for(int i=0;i<array.getSize();i++){
res.append(array.get(i));
if(i!=array.getSize()-1)
res.append(',');
}
res.append("] tail");
return res.toString();
}
public static void main(String[] args) {
ArrayQueue<Integer> queue = new ArrayQueue<>();
for (int i=0;i<10;i++){
queue.enqueue(i);
System.out.println(queue);
if(i%3 == 2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}
出队操作时,每一次把数组的第一个元素(索引为0拿出后),后面所有的元素都要向前移(动态数组的removefirst逻辑中),但如果数组承载的元素是百万级,出队要进行百万级的时间消耗,这样会耗费很长的时间。
- 如何改进使得出队和入队的循环复杂度都为O(1)?
循环对垒
3-6 循环队列
删除后不挪动其他元素,基于这样的思想,我们记录队首是谁。
当队首的元素移出后,我们只需要更改front的指向就行了,即front++
front指向第一个元素,tail指向最后一个元素的下一个位置。
入队
出队
循环在哪里?
tail=(tail+1)%数组长度
3-7 循环队列的实现
import java.util.Objects;
public class LoopQueue<E> implements Queue<E> {
public E[] data;
private int front,tail;
private int size;
public LoopQueue(int capacity){
data = (E[])new Object[capacity+1];
front = 0;
tail = 0;
size = 0;
}
public LoopQueue(){
this(10);
}
public int getCapacity(){
return data.length-1;//capacity中这里浪费一个空间
}
@Override
public boolean isEmpty(){
return front == tail;
}
@Override
public int getSize(){
return size;
}
@Override
public void enqueue(E e){
if( (tail+1)%data.length== front)
resize(getCapacity()*2);
data[tail]=e;
tail=(tail+1)%data.length;
size++;
}
private void resize(int newCapacity){
E[] newData = (E[])new Object[newCapacity+1];
for (int i=0;i<size;i++)
newData[i]=data[(front+i)%data.length];
data = newData;
front = 0;
tail = size;
}
@Override
public E dequeue(){
if(isEmpty())
throw new IllegalArgumentException("can not dequeue from an empty queue.");
E ret = data[front];
data[front]=null;
front = (front+1)%data.length;
size--;
if(size == getCapacity()/4 && getCapacity()/2!=0)
resize(getCapacity()/2);
return ret;
}
@Override
public E getFront(){
if(isEmpty())
throw new IllegalArgumentException("Queue is empty.");
return data[front];
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
res.append("front [");
for(int i = front ; i != tail ; i = (i + 1) % data.length){
res.append(data[i]);
if((i + 1) % data.length != tail)
res.append(", ");
}
res.append("] tail");
return res.toString();
}
public static void main(String[] args){
LoopQueue<Integer> queue = new LoopQueue<>();
for(int i = 0 ; i < 10 ; i ++){
queue.enqueue(i);
System.out.println(queue);
if(i % 3 == 2){
queue.dequeue();
System.out.println(queue);
}
}
}
}
3-8 数组队列和循环队列的比较
import java.util.Random;
public class Main {
// 测试使用q运行opCount个enqueueu和dequeue操作所需要的时间,单位:秒
private static double testQueue(Queue<Integer> q,int opCount){
long startTime = System.nanoTime();
Random random = new Random();
for(int i=0;i<opCount;i++)
q.enqueue(random.nextInt(Integer.MAX_VALUE));
for(int i=0;i<opCount;i++)
q.dequeue();
long endTime = System.nanoTime();
return (endTime-startTime)/1000000000.0;
}
public static void main(String[] args) {
int opCount = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double time1= testQueue(arrayQueue,opCount);
System.out.println("ArrayQueue,time: "+time1+"s");
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double time2=testQueue(loopQueue,opCount);
System.out.println("LoopQueue time: "+time2+"s");
}
}
最好运行多次取平均值。
运行性能和jvm关系非常大,不同的java版本可能与理论有些不同。
但是O(n)与O(1)的差别还是非常大
队列的应用
- 广度优先遍历