0034数据结构之栈和队列

------------------------------栈-----------------------------------------------------

栈可以理解为是数组的子集,只能从一端添加元素,且从同一端删除元素,这一端称为栈顶。

last in first out : LIFO 后进先出

栈的实现主要有如下内容:

Stack<E>

push(E)

E pop()

E peek()

int getSize

boolean isEmpty()

定义接口Stack,接口中含有如上5种方法,定义实现类ArrayStack,由于是数组实现,所以可以在实现中额外增加一个获取容量的方法getCapacity() 

接口Stack定义如下:

package stack;

public interface Stack<E> {
    public int getSize();
    public boolean isEmpty();
    public void push(E e);
    public E pop();
    public E peek();
}

实现类ArrayStack如下:

package stack;

import array.Array;

public class ArrayStack<E> implements Stack<E> {
    //基于自定义的Array类实现栈
   
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();
    }

    @Override
    public void push(E e) {
        array.addLast(e);
    }

    @Override
    public E pop() {
        return array.removeLast();
    }

    @Override
    public E peek() {
        return array.getLast();
    }

    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Stack:[");
        int size = array.getSize();
        for(int i=0;i<size;i++){
            sb.append(array.get(i));
            if(i != size-1){
                sb.append(",");
            }
        }
        sb.append("] top");
        return sb.toString();
    }
}

测试代码如下:

package stack;

public class Main {
    public static void main(String[] args) {
        Stack<Integer> stack = new ArrayStack<>(8);
        for(int i=0;i<5;i++){
            stack.push(i);
            System.out.println(stack);
        }
        stack.pop();
        System.out.println(stack);
        System.out.println(stack.peek());
    }
}

-------------------------------------队列-------------------------------------------------------------------------

队列也是线性结构(数据排成一排),队列的操作也是数组操作的子集,只能一端添加元素(队尾),另一端(队首)取出元素。

队列的定义如下:

Queue<E>

void enqueue(E) 入队 相当于栈的push

E dequeue()  出队   相当于栈的 pop

E getFront()  查看队首元素   相当于栈的peek

int getSize()

boolean isEmpty()

实现类ArrayQueue代码如下:

package queue;

import array.Array;

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();
    }

    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Queue:head[");
        int size = array.getSize();
        for(int i=0;i<size;i++){
            sb.append(array.get(i));
            if(i != size-1){
                sb.append(",");
            }
        }
        sb.append("] tail");
        return sb.toString();
    }

    public static void main(String[] args) {
        ArrayQueue<Integer> arrayQueue = new ArrayQueue<>(8);
        for(int i=0;i<5;i++){
            arrayQueue.enqueue(i);
            System.out.println(arrayQueue);
        }
        arrayQueue.dequeue();
        System.out.println(arrayQueue);
        System.out.println(arrayQueue.getFront());
        System.out.println(arrayQueue);
    }
}

 

复杂度分析:

入队enqueue:O(1) 均摊

出队dequeue:O(n)

查询队首元素getFront:O(1)

查询元素个数getSize:O(1)

判断队列是否为空isEmpty:O(1)

问题:所以对于出队,如果队列中的数量较大的话,会很耗时,如何解决呢?

解决办法:循环队列

front==tail 则队列为空,(tail+1)%data.length==front则队列为满,需要有意识的浪费一个空间,让最后一次tail可以指向这个位置,所以构造函数时传capacity参数应该创建capacity+1长度的数组

两种遍历方式:

方式一:for(int i=0;i<size;i++){实现体里取偏移量}

方式二:for(int i=front;i!=tail;i=(i+1)%data.length)

注意好什么位置用的data.length,什么位置用的getCapacity()

1)  初始创建的队列,front和tail、size都指向0

2)  先写resize方法 

3)  再写入队方法 

4)  出队 

5)  查看队首元素

6)  重写toString()方法

7)进行测试

8)数组队列与循环队列的性能测试

具体代码实现如下:

package queue;

public class LoopQueue<E> implements Queue<E> {
    private E[] data;
    private int front;
    private int tail;
    private int size;

    public LoopQueue(int capacity){
        data = (E[])new Object[capacity+1];
        front = 0;
        tail = 0;
        size = 0;
    }

    public LoopQueue(){
        this(8);
    }


    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return front==tail;
    }

    //getCapacity返回用户定义的容量,data.lengthgetCapacity1,是有意多留一个空间给tail最后一次指向使用
   
public int getCapacity(){
        return data.length-1;
    }

    //为了方便记忆,可记住取余运算的时候全都是用的data.length,其他时候用getCapacity()

   
public void resize(int newCapacity){
        //注意此处要与构造函数保持一直,需要定义newCapacity+1个长度
       
E[] newData = (E[])new Object[newCapacity +1];
        for(int i=0;i<size;i++){
            //此处取余用data.length
           
newData[i] = data[(i+front)%data.length];
        }
        data = newData;
        //注意resize之后size大小不变,但是要重新定位fronttail的位置
       
front = 0;
        tail = size;
    }

    @Override
    public void enqueue(E e) {
        //入队时需要先判断队列是否已满,如果满了需要先扩容,此处也是用data.length做取余运算
       
if((tail+1)%data.length==front){
            //resize传的参数要用getCapacity,而不能用data.length
           
resize(2*getCapacity());
        }
        //如果未到扩容条件,则tail指向的位置就是需要入队的位置
       
//而如果达到扩容条件,进行了扩容,则在扩容方法中,已经重新定位了fronttail的位置,
       
// front指向0tail指向size位置,也是需要入队的最新位置
       
data[tail] = e;
        //入队后需要更改tail的位置和size的大小
       
tail = (tail+1)%data.length;
        size++;
    }

    @Override
    public E dequeue() {
        //判断如果队列为空,则抛出异常,也可改为返回空值
       
if(isEmpty()){
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        }
        //取出队首元素以便最后返回
       
E e = data[front];
        //取出队首元素后,要将队首元素置为null
       
data[front] = null;
        //只需要更改front的位置和size的大小即可
       
front = (front+1)%data.length;
        size--;
        //判断是否需要缩容
       
if(size == getCapacity()/4 && getCapacity()/2 != 0){
            resize(getCapacity()/2);
        }
        return e;
    }

    @Override
    public E getFront() {
        return data[front];
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("LoopQueue:size = %d ,capacity = %d \n",size,getCapacity()));
        sb.append("front[");
        //第二种方式 遍历循环队列中的数据
       
for(int i=front;i !=tail;i=(i+1)%data.length){
            sb.append(data[i]);
            //如果不是最后一个元素
           
if((i+1)%data.length != tail){
                sb.append(",");
            }
        }
        sb.append("]tail");
        return sb.toString();
    }

    public static void main(String[] args) {
        LoopQueue<Integer> loopQueue = new LoopQueue<>();
        for(int i=0;i<10;i++){
            loopQueue.enqueue(i);
            System.out.println(loopQueue);
            //每入队三个元素,就出队一个元素
           
if(i%3 == 2){
                loopQueue.dequeue();
                System.out.println(loopQueue);
            }
        }

    }
}

时间复杂度:

enqueue: O(1) 均摊:涉及到扩容

dequeue: O(1) 均摊:涉及到缩容

getFront: O(1)

getSize: O(1)

isEmpty: O(1)

测试数组队列和循环队列的性能差别:

package queue;

import java.util.Random;

public class Main {
    public static double testQueue(Queue<Integer> queue,int count){
        Random random = new Random();
        long startTime = System.nanoTime();
        for(int i=0;i<count;i++){
            queue.enqueue(random.nextInt());
        }
        for(int i=0;i<count;i++){
            queue.dequeue();
        }
        long endTime = System.nanoTime();
        double cost = (endTime - startTime)/1000000000.0;
        return  cost;
    }

    public static void main(String[] args) {
        int count = 100000;
        ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
        double cost1 = testQueue(arrayQueue,count);
        System.out.println(cost1);//12.8839
       
LoopQueue<Integer> loopQueue = new LoopQueue<>();
        double cost2 = testQueue(loopQueue,count);
        System.out.println(cost2);//0.0100
   
}

当队列中存放的数据量较大,且频繁出队时,循环队列的性能明显由于数组队列。
}

 

若有理解不到之处,望指正!

 

猜你喜欢

转载自www.cnblogs.com/xiao1572662/p/12095212.html