栈和队列
- 栈也是一种线性结构
- 相比数组,栈对应的操作是数组的子集
- 只能从一端添加元素,也只能从一端取出元素
- 这一端称为栈顶
- 栈是一种后进先出的数据结构(LIFO)
举一个例子,我们平常敲代码都经常撤销,而撤销操作的原理就是靠栈来实现的。
比如我们先打出 "沉迷"、"学习"、"不法",这个时候我们发现打错了无法两个字,而这个时候栈是这样的。
通过使用撤销功能,不法将从栈顶取出,然后删除不法两个字。这个时候栈中就只有两个元素。
再举一个深入的例子:程序调用的系统栈
假设我们有三个函数。
我们通过调用函数A,在执行第二行执行B函数的时候,暂停函数A。这个时候就会将暂停的函数A压入栈中。
然后调用函数B里的C函数时候,也会将函数B压入栈中,
当函数C执行完了以后,会首先取出栈中的B2,然后继续执行B2剩下的函数,执行完成以后执行A2。
栈的基本实现
定义一个Stack接口和实现类
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(){
array = new Array<>();
}
public ArrayStack(int capacity){
array = new Array<>(capacity);
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
//添加到栈顶
@Override
public void push(E e) {
array.addLast(e);
}
//取出并移除栈顶元素
@Override
public E pop() {
return array.removeLast();
}
//查看栈顶元素
@Override
public E peek() {
return array.get(array.getSize() - 1);
}
public int getCapacity(){
return array.getCapacity();
}
@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> arrayStack = new ArrayStack<>();
for (int i = 0; i < 5; i++) {
arrayStack.push(i);
System.out.println(arrayStack);
}
System.out.println("取出栈顶元素"+arrayStack.pop());
System.out.println(arrayStack);
}
}
Stack: [0] top
Stack: [0, 1] top
Stack: [0, 1, 2] top
Stack: [0, 1, 2, 3] top
Stack: [0, 1, 2, 3, 4] top
取出栈顶元素4
Stack: [0, 1, 2, 3] top
栈的复杂度分析
ArrayStack<E>
- void push(E)
O(1)
- E pop()
O(1)
- E peek()
O(1)
- int getSize()
O(1)
- boolean isEmpty()
O(1)
栈的应用-括号匹配器
引用 LeetCode 上 20. 有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 注意空字符串可被认为是有效字符串。
具体思路就是判断当前是左侧括号还是右侧,如果是左侧就压入栈,当是右侧的时候就取出栈顶元素进行依次匹配。
class Solution {
public boolean isValid(String s) {
if(s.isEmpty()) {
return true;
}
if (s.length() % 2 != 0){
return false;
}
Stack<Character> stack=new Stack<>();
for(char c:s.toCharArray()){
if(c=='(') {
stack.push(')');
} else if(c=='{') {
stack.push('}');
} else if(c=='[') {
stack.push(']');
} else if(stack.empty()||c!=stack.pop()) {
return false;
}
}
return stack.empty();
}
}
队列的基本实现
- 队列也是一种线性结构
- 相比数组,队列对应的操作是数组的子集
- 只能从队尾添加元素,只能从队首取出元素
- 队列是一种先进先出的数据结构(FIFO)
很容易理解,和生活中的排队是一样的。
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array;
public ArrayQueue() {
array = new Array<>();
}
public ArrayQueue(int capacity) {
array = new Array<>(capacity);
}
@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.get(0);
}
public int getCapacity(){
return array.getCapacity();
}
@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();
}
}
数组队列的复杂度分析
ArrayQueue<E>
- void enqueue(E)
O(1)
- E dequeue()
O(n)
- E front()
O(1)
- int getSize()
O(1)
- boolean isEmpty
O(1)
循环队列
由于数组队列的出队操作是O(n)
的,是存在一定的局限性,使用循环队列来解决这个问题。
由于我们在删除索引为0的数据时候,会导致后面的数据前移,所以是O(n)
级别。
循环队列其实就是在队首移除的时候,我们不进行后面数据的移动,而是使用front变量进行自增,然后指向下一个元素,tail变量来维护队尾的位置。
一开始的时候,front和tail都指向索引0的位置。
当新增一个元素后,tail进行自增然后指向下一个元素的位置,而front保持不变。
而移除一个队首元素的时候,front要进行自增然后指向下一个元素的位置,tail保持不变,这样的话我们不需要移动对象,只需要更改front的指向即可。
那为什么叫循环队列呢,因为我们当队列满了的时候,我们需要进行(tail+1) % capacity
操作,然后将tail指向新的位置。
当我们在0的位置再新增一个元素的时候,我们如何判断队列是否满了呢,因为我们之前说过 front == tail的时候说明数组为空,而我们使用循环队列的话,我们需要使用 (tail + 1) % capacity == front
就说明队列满了需要进行扩容。需要说明循环队列会浪费一个空间。
循环队列的实现
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front, tail;
private int size;
public LoopQueue(int capacity) {
//因为循环队列需要浪费一个空间,所以要将数组大小+1
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
public LoopQueue() {
this(10);
}
public int getCapacity() {
return data.length - 1;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front == tail;
}
@Override
public void enqueue(E e) {
if ((tail + 1) % data.length == front) {
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
size++;
}
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot 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("Cannot dequeue from an empty queue.");
}
return data[front];
}
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < size; i++) {
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = size;
}
@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\n");
res.append("front = "+front);
res.append(", tail = "+tail);
res.append(", data.length = "+data.length);
return res.toString();
}
}
循环队列的复杂度分析
LoopQueue<E>
- void enqueue(e)
O(1)
- E dequeue()
O(1)
- E getFront()
O(1)
- int getSize()
O(1)
- boolean isEmpty()
O(1)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。