一、栈
(一)栈的基本实现
接口Stack:
public interface Stack {
int getSize();
boolean isEmpty();
void push(T e);
T pop();
T peak();
}
ArrayStack继承Stack这个接口,使用之前定义过的Array类:
public class ArrayStack implements Stack{
//基于数组实现
Array<T> array;
//构造函数,初始化对栈的容量
public ArrayStack(int capacity){
array = new Array<>(capacity);
}
//无参构造函数,默认容量为10
public ArrayStack(){
array = new Array<>();
}
//取得栈中元素个数
@Override
public int getSize(){
return array.getSize();
}
//
public int getCapacity(){
return array.getCapacity();
}
//判断栈是否为空
@Override
public boolean isEmpty(){
return array.isEmpty();
}
//元素进栈
@Override
public void push(T e){
array.addLast(e);
}
//元素出栈,并返回出栈的元素
@Override
public T pop(){
return array.removeLast();
}
//查看栈顶元素
@Override
public T peak(){
return array.getLast();
}
//重写Object类的toString方法
@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、编译器、word等应用的撤销(Undo)操作
输入“今天要认真学习”,那么栈中保存的为:[今,天,要,认,真,学,习] top
如果想输入的是“今天也要认真学习”,在删除过程中,出栈次序为:习,学,真,认,要
再输入“也要认真学习”即可
2、系统栈
在函数调用的时候,如果出现了嵌套调用,系统栈会记录调用过的函数,如:
A(){
B();
}
B(){
C();
}
C(){
f();
}
A,B,C依次进栈,调用函数A时,运行到函数B,则跳转到函数B,执行B中的代码,接着又调用函数C,跳转到C中,执行了函数f,
在f执行完后,程序跳转到C中继续运行,C出栈,C运行完后,跳转到B,直到跳转到A,运行完所有代码,程序结束
递归函数也运用了栈的原理
3、括号匹配操作
遇到左括号则进栈,遇到右括号则判断栈顶的左括号是否与之匹配
public class IsValid {
public static void main(String[] args) {
ArrayStack<Character> stack = new ArrayStack<>();
String s="{([]}";
for(int i=0;i<s.length();i++){
char c=s.charAt(i);
if(c=='{'||c=='['||c=='(')
stack.push(c);
else{
if (stack.isEmpty())
System.out.println(false);
if(c=='}'&&stack.pop()!='{')
System.out.println(false);
if(c==']'&&stack.pop()!='[' )
System.out.println(false);
if(c==')'&&stack.pop()!='(')
System.out.println(false);
}
}
System.out.println(stack.isEmpty());
}
}
(三)时间复杂度分析
入栈(push)和出栈(pop)都在从栈顶进行操作,时间复杂度均为O(1)
二、队列
(一)数组队列队列的基本实现
接口Queue:
public interface Queue {
int getSize();
boolean isEmpty();
void enqueue(T e);
T dequeue();
T getFront();
}
ArrayQueue类实现Queue接口:
public class ArrayQueue implements Queue{
Array<T> 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(T e){
array.addLast(e);
}
@Override
public T dequeue(){
return array.removeFirst();
}
@Override
public T 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();
}
}
与栈不同的是,数组队列先进先出,而出队操作的时间复杂度为O(n),每出队一个元素,后面的元素都要向前移一位,性能就很差了
借助循环队列,可以使出队操作的时间复杂度降为O(1)
(二)循环队列
public class LoopQueue implements Queue {
private T[] data;
private int front,tail;
private int size;
//实际上,循环队列data的容量要比用户指定的容量大一,才能满足保存capacity+1个元素
public LoopQueue(int capacity){
data = (T[]) new Object[capacity + 1];
front=0;
tail=0;
size=0;
}
public LoopQueue(){
this(10);
}
@Override
public int getSize(){
return size;
}
@Override
public boolean isEmpty(){
return tail == front;
}
//队列长度为capacity+1,但只能保存capacity个元素
public int getCapacity(){
return data.length-1;
}
//入队操作
@Override
public void enqueue(T e){
//tail+1==front时,队列已满,tail==front第队列为空
if((tail + 1) % data.length == front)
resize(getCapacity() * 2);
//只需将元素存入tail指定的位置,tail再向后移一位即可
data[tail]=e;
tail = (tail+1)%data.length;//对data.length取余,实现了队列的循环
size++;
}
@Override
public T dequeue(){
if(isEmpty())
throw new IllegalArgumentException("dequeue is empty");
T ret = data[front];//保存队首元素并将其清除
data[front]=null;
front = (front + 1) % data.length;//front向后移一位,被取走元素的下一个元素成为队首元素
size--;
if(size == getCapacity() / 4 && getCapacity() / 2 != 0)
resize(getCapacity() / 2);
return ret;
}
@Override
public T getFront(){
if(isEmpty())
throw new IllegalArgumentException("dequeue is empty");
return data[front];
}
public void resize(int newCapacity){
T[] newData = (T[])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("LoopQueue: 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();
}
}
每次入队,出队都只用把tail或者front向后移一位,即便调用了resize,其均摊复杂度也都是O(1),较数组队列性能有了很大的提升
(三)数组队列和循环队列的比较
分别对数组队列,循环队列进行100000次入队、出队操作:
import javax.imageio.metadata.IIOMetadataFormatImpl;
import java.util.Random;
public class OpTime {
public static double testQueue(Queue q,int opCount){
Random random=new Random();
long start,end;
start=System.currentTimeMillis();//入队、出队操作开始前的时间,毫秒级别
for(int i=0;i<opCount;i++)
q.enqueue(random.nextInt(Integer.MAX_VALUE));
for(int i=0;i<opCount;i++)
q.dequeue();
end=System.currentTimeMillis();//入队,出队结束后的时间
return (end - start) / 1000.0;
}
public static void main(String[] args) {
int opCount=100000;
ArrayQueue<Integer> arrayQueue=new ArrayQueue<>();
System.out.println(testQueue(arrayQueue,opCount));
LoopQueue<Integer> loopQueue=new LoopQueue<>();
System.out.println(testQueue(loopQueue,opCount));
}
}
运行结果:
可以看出,ArrayQueue在程序中的时间复杂度是n^2级别的,而LoopQueue的复杂度为常数级别