数据结构——栈和队列

一、栈
(一)栈的基本实现
接口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的复杂度为常数级别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值