栈与队列的区别及自定义实现

第一部分的总结主要转载:https://blog.csdn.net/bbc955625132551/article/details/72773285

一、栈(Stack)和队列(Queue)的特点

栈(Stack)和队列(Queue)是两种基于数组实现的,操作受限的线性表,即栈和队列都是数组的子集。

线性表:线性表是一种线性结构,它是一个含有n≥0个结点的有限序列,同一个线性表中的元素数据类型相同并且满足“一对一”的逻辑关系。

“一对一”的逻辑关系:指的是对于线性表中的结点,有且仅有一个开始结点没有前驱但有一个后继结点,有且仅有一个终端结点没有后继但有一个前驱结点,其它的结点都有且仅有一个前驱和一个后继结点。

这种受限表现在:

  • 栈的插入和删除操作只允许在表的尾端进行(在栈中成为“栈顶”),满足LOFI(Last Out First In )
  • 队列只允许在表尾插入数据元素,在表头删除数据元素,满足FIFO(First In First Out)

栈与队列的相同点:

1.都是线性结构。

2.插入操作都是限定在表尾进行。

3.都可以通过顺序结构和链式结构实现。

4.插入与删除的时间复杂度都是O(1),在空间复杂度上两者也一样。

5.多链栈和多链队列的管理模式可以相同。

栈与队列的不同点:

1.删除数据元素的位置不同,栈的删除操作在表尾进行,队列的删除操作在表头进行。

2.应用场景不同;常见栈的应用场景包括括号问题的匹配,表达式的转换和求值,函数调用和递归实现,深度优先搜索遍历等;常见的队列的应用场景包括计算机系统中各种资源的管理,消息缓冲器的管理和广度优先搜索遍历等。

3.顺序栈能够实现多栈空间共享,而顺序队列不能。

二、自定义栈的实现

1、定义Stack<E>接口

public interface Stack<E> {

    // 入栈(压栈),将元素放入栈中
    void push(E e);

    // 出栈(弹栈),从栈中取出元素
    E pop();

    // 查看栈顶的元素
    E peek();

    // 获取栈的大小
    int getSize();

    // 判断栈是否为空
    boolean isEmpty();
}

2、实现类ArrayStack<E>

public class ArrayStack<E> implements Stack<E> {

    MyArray<E> array;

    public ArrayStack() {
        // 默认容量为10
        this.array = new MyArray<E>();
    }

    public ArrayStack(int capacity) {
        this.array = new MyArray<E>(capacity);
    }

    // 压栈
    public void push(E e) {
        array.addLast(e);
    }

    // 取出栈顶元素
    public E pop() {
        return array.removeLast();
    }

    // 查看栈顶的元素
    public E peek() {
        return array.get(array.getSize() - 1);
    }

    // 获取栈的大小
    public int getSize() {
        return array.getSize();
    }

    // 判断栈是否为空
    public boolean isEmpty() {
        return array.isEmpty();
    }

    // 获取栈的容量
    public int getCapacity() {
        return array.getCapacity();
    }

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

        }
        sb.append("] top");
        return sb.toString();
    }
}

提示:应用场景,编辑器的do/undo操作、JVM虚拟机栈、配合程序计数器,确定方法执行的顺序

三、自定义队列的实现

分别实现两种队列,即普通队列、和循环队列

主要区别在时间复杂度上,在实现队列的动态扩减容后,普通队列每次操作unQueue()方法,其时间复杂度都是O(n),而循环队列每次操作unQueue()方法,其时间复杂度都是O(1)。

1、自定义接口

public interface Queue<E> {

    // 获取队列的大小
    int getSize();

    // 判断队列是否为空
    boolean isEmplt();

    // 将元素放入队列
    void enqueue(E e);

    // 取出队列元素
    E dequeue();

    E getFront();

}

2、普通队列的实现

public class ArrayQueue<E> implements Queue<E> {

    private MyArray<E> array;

    public ArrayQueue() {
        this.array = new MyArray<E>();
    }

    public ArrayQueue(int capacity) {
        this.array = new MyArray<E>(capacity);
    }

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

    public boolean isEmplt() {
        return array.isEmpty();
    }

    public void enqueue(E e) {
        // 此操作的时间复杂度,均摊
        array.addLast(e);
    }

    public E dequeue() {
        // 根据输入的特性,此操作的时间复杂度为O(n)
        return array.removeFirst();
    }

    public E getFront() {
        return array.get(0);
    }

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

3、循环队列的实现

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

    // 默认队列的容量为10
    public LoopQueue() {
        this(10);
    }

    // 获取队列大小
    public int getSize() {
        return size;
    }

    // 获取队列容量,因为根据需要数据长度加一,此时返回数组容量需要减一
    public int getCapacity() {
        return data.length - 1;
    }

    // 判断队列是否为空,若front、tail指向同一个下标,即数组为空
    public boolean isEmplt() {
        return front == tail;
    }

    // 向队列中添加元素
    public void enqueue(E e) {
        if ((tail + 1) % data.length == front) {
            resize(getCapacity() * 2);
        }
        data[tail] = e;
        tail = (tail + 1) % data.length;
        size++;
    }

    // 出队操作
    public E dequeue() {
        if (this.isEmplt()) {
            throw new IllegalArgumentException("The Queue is empty,can not dequeue any element");
        }
        // 获取队列第一个元素
        E frontEle = data[front];
        // 将被取出的元素置为null
        data[front] = null;
        // 更新front,记录队列的下一个元素的下标为front
        front = (front + 1) % data.length;
        size--;
        // 实现动态减容
        if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
            resize(getCapacity() / 2);
        }
        return frontEle;
    }

    // 实现数组动态扩减容
    private void resize(int newCapacity) {
        E[] newArray = (E[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            newArray[i] = data[(i + front) % data.length];
        }
        data = newArray;
        front = 0;
        tail = size;

    }

    // 获取队列的元素
    public E getFront() {
        if (this.isEmplt()) {
            throw new IllegalArgumentException("Queue is empty");
        }
        return data[front];
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
        sb.append("front [");
        // 当front != tail时,表示队列中存在元素
        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();
    }
}

4、时间复杂度测试

public class QueueTest {

    public static double testQueue(Queue<Integer> queue, int count) {
        // 开始时间,单位纳米
        long startTime = System.nanoTime();
        Random random = new Random();
        for (int i = 0; i < count; i++)
            queue.enqueue(random.nextInt(Integer.MAX_VALUE));
        for (int i = 0; i < count; i++) {
            queue.dequeue();
        }
        // 结束时间,单位纳米
        long endTime = System.nanoTime();
        // 程序运行消耗时间
        return (endTime - startTime) / 1000000000.0;
    }

    public static void main(String[] args) {
        // 定义操作的次数
        int opNumber = 100000;
        ArrayQueue<Integer> arrayQueue = new ArrayQueue<Integer>();
        // 普通队列
        double simQueue = testQueue(arrayQueue, opNumber);
        System.out.println("ArrayQueue, time: " + simQueue + " s");
        // 虚幻队列
        LoopQueue<Integer> loopQueue = new LoopQueue<Integer>();
        double lpQueue = testQueue(loopQueue, opNumber);
        System.out.println("LoopQueue, time: " + lpQueue + " s");

        System.out.println("运行时间相差倍数为times = " + (int) (simQueue / lpQueue));
    }
}

测试结果:

针对运行环境的不同,可能测试结果会有些出处,但是运行时间的差异已经显现无疑

猜你喜欢

转载自blog.csdn.net/jinYwuM/article/details/82530127
今日推荐