一.栈的基本实现
Stack(E)
- void push(E) 推入栈
- E pop() 移出栈
- E peek() 得到栈顶元素
- int getSize() 栈中元素个数
- boolean isEmpty 栈是否为空
我们的栈的实现是基于上一篇博文的动态数组的。
文件结构:
.
├── Array.iml
├── out
│ └── production
└── src
├── Array.java
├── ArrayStack.java
├── Main.java
└── Stack.java
新建接口文件Stack.java:
public interface Stack<E> {
int getSize();
boolean isEmpty();
void push(E e);
E pop();
E peek();
}
编写ArrayStack.java:
public class ArrayStack<E> implements Stack<E> {
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();
}
/**
* 不是接口中的一部分, 因为用动态数组实现的栈才有这个方法
* @return
*/
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"); //表示末尾是top
return res.toString();
}
}
其中peek方法调用的getLast方法,在我们原本的动态数组中没有定义的, 我们补充一下。
Array.py,在get方法后面补充上:
E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Get failed.Index is illegal.");
}
return data[index];
}
E getLast(){
return get(size - 1);
}
E getFirst(){
return get(0);
}
最后Main.java中测试
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);
}
}
运行结果:
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
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)
二.栈的一个应用:括号的匹配
列举几个栈的应用:
- undo操作 (编辑器)
- 系统调用栈 (操作系统)
- 括号匹配 (编译器)
我们来详解一下括号匹配的应用原理
拿leetcode上的一个简单算法题为例:
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
解答:
import java.util.Stack; //java提供的标准库的栈, 接口与我们自定义的一样
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("{}()[]");
}
}
python的答案
class Solution:
def isValid(self, s):
"""
:type s: str
:rtype: bool
"""
stack = []
for c in s:
if c == '(' or c == '[' or c == '{':
stack.append(c)
else:
if len(stack) == 0:
return False
topchar = stack.pop()
if c == ')' and topchar != '(':
return False
if c == ']' and topchar != '[':
return False
if c == '}' and topchar != '{':
return False
return len(stack)==0
三. 数组队列
- 队列也是一种线性结构,先进先出
- 相比数组, 队列对应的操作是数组的子集
- 只能从一端(队尾)添加元素, 只能从另一端(队首)取出元素
队列的实现
Queue<E>
- void enqueue(E) 入队
- E dequeue() 出队
- E getFront() 查看队首元素
- int getSize()
- boolean isEmpty()
依然基于我们的自定义动态数组实现.
新建接口文件Queue.java
public interface Queue<E> {
void enqueue(E e);
E dequeue();
E getFront();
int getSize();
boolean isEmpty();
}
ArrayQueue.java:
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();
}
public int getCapacity() {
return array.getcapacity();
}
@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);
}
}
}
}
测试结果:
Queue:front [0] tail
Queue:front [0,1] tail
Queue:front [0,1,2] tail
Queue:front [1,2] tail
Queue:front [1,2,3] tail
Queue:front [1,2,3,4] tail
Queue:front [1,2,3,4,5] tail
Queue:front [2,3,4,5] tail
Queue:front [2,3,4,5,6] tail
Queue:front [2,3,4,5,6,7] tail
Queue:front [2,3,4,5,6,7,8] tail
Queue:front [3,4,5,6,7,8] tail
Queue:front [3,4,5,6,7,8,9] tail
时间复杂度分析
ArrayQueue<E>
- void enqueue<E> O(1)均摊复杂度
- E dequeue() O(n)
- E front() O(1)
- int getSize() O(1)
- boolean isEmpty() O(1)
四. 循环队列
dequeue的时间复杂度是O(n)导致了队列的局限性。我们使用循环队列。
具体实现:
新建LoopQueue.java
public class LoopQueue<E> implements Queue<E> {
private 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(20);
}
public int getCapacity() {
return data.length - 1;
}
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 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++;
}
@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) { // 3/4的空间是空的, 我们缩容
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, data.length));
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);
}
}
}
}
复杂度分析
LoopQueue<E>
- void enqueue<E> O(1)均摊复杂度
- E dequeue() O(1)均摊复杂度
- E front() O(1)
- int getSize() O(1)
- boolean isEmpty() O(1)
可以看到LoopQueue对比ArrayQueue在dequeue方法的时间复杂度,由O(n)变为O(1).
LoopQueue与ArrayQueue对比测试
Main.java
import java.util.Random;
public class Main {
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);
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double time2 = testQueue(loopQueue, opCount);
System.out.println("LoopQueue, time: "+time2);
}
}
运行结果:
ArrayQueue, time: 42.833585979
LoopQueue, time: 0.019467203
性能差距很大。