zoukankan      html  css  js  c++  java
  • java使用数组和链表实现栈和队列

    前言

    栈(Stack)是一种后进先出的数据结构,仅允许在栈顶插入、删除、读取。队列(Queue)是一种先进先出的数据结构,队头读取、删除,队尾插入。

    使用数组实现栈

    使用到的MyArrayList和MyLinkedList详情请查看 java实现一个自己的ArrayList和LinkedList

    public interface Stack<E> {
    
      /**
       * 栈是否为空
       */
      boolean isEmpty();
    
      /**
       * 栈顶添加元素
       */
      void push(E e);
    
      /**
       * 栈顶删除元素
       */
      E pop();
    
      /**
       * 查询栈顶元素
       */
      E peek();
    }
    

    定义栈的接口

    /**
     * 使用数组实现栈
     */
    public class ArrayStack<E> implements Stack<E> {
    
      /**
       * 代理对象
       */
      private List<E> delegate;
    
      public ArrayStack(int capacity) {
        delegate = new MyArrayList<>(capacity);
      }
    
      public ArrayStack() {
        delegate = new MyArrayList<>();
      }
    
      @Override
      public boolean isEmpty() {
        return delegate.isEmpty();
      }
    
      @Override
      public void push(E e) {
        delegate.add(e);
      }
    
      @Override
      public E pop() {
        return delegate.remove(delegate.size() - 1);
      }
    
      @Override
      public E peek() {
        return delegate.get(delegate.size() - 1);
      }
    
      @Override
      public String toString() {
        return delegate.toString();
      }
    }
    

    使用链表实现栈

    /**
     * 使用链表实现栈
     */
    public class LinkedStack<E> implements Stack<E> {
    
      private List<E> delegate;
    
      public LinkedStack() {
        delegate = new MyLinkedList<>();
      }
    
      @Override
      public boolean isEmpty() {
        return delegate.isEmpty();
      }
    
      @Override
      public void push(E e) {
        delegate.add(e);
      }
    
      @Override
      public E pop() {
        return delegate.remove(delegate.size() - 1);
      }
    
      @Override
      public E peek() {
        return delegate.get(delegate.size() - 1);
      }
    
      @Override
      public String toString() {
        return delegate.toString();
      }
    }
    

    使用数组实现队列

    public interface Queue<E> {
    
      /**
       * 队列是否为空
       */
      boolean isEmpty();
    
      /**
       * 入队
       */
      void enqueue(E e);
    
      /**
       * 出队
       */
      E dequeue();
    
      /**
       * 查询队头元素
       */
      E peek();
    }
    

    定义接口

    /**
     * 使用数组实现队列
     */
    public class ArrayQueue<E> implements Queue<E> {
    
      /**
       * 代理对象
       */
      private List<E> delegate;
    
      public ArrayQueue(int capacity) {
        delegate = new MyArrayList<>(capacity);
      }
    
      public ArrayQueue() {
        delegate = new MyArrayList<>();
      }
    
      @Override
      public boolean isEmpty() {
        return delegate.isEmpty();
      }
    
      @Override
      public void enqueue(E e) {
        delegate.add(e);
      }
    
      @Override
      public E dequeue() {
        return delegate.remove(0);
      }
    
      @Override
      public E peek() {
        return delegate.get(0);
      }
    
    
      @Override
      public String toString() {
        return delegate.toString();
      }
    }
    

    使用链表实现队列

    /**
     * 使用链表实现队列
     */
    public class LinkedQueue<E> implements Queue<E> {
    
      /**
       * 代理对象
       */
      private List<E> delegate;
    
      public LinkedQueue() {
        delegate = new MyLinkedList<>();
      }
    
      @Override
      public boolean isEmpty() {
        return delegate.isEmpty();
      }
    
      @Override
      public void enqueue(E e) {
        delegate.add(e);
      }
    
      @Override
      public E dequeue() {
        return delegate.remove(0);
      }
    
      @Override
      public E peek() {
        return delegate.get(0);
      }
    
    
      @Override
      public String toString() {
        return delegate.toString();
      }
    }
    

    使用数组实现循环队列

    对于使用数组实现的队列来说,每次出队都需要将所有队头之后的元素往前移动一位,时间复杂度为O(n),这种情况我们可以使用循环队列来优化,

    使用两个指针表示队头(front)和队尾(rear),每次出队不移动元素,只移动队头指针。
    为了区分队空和队满的情况,有两种处理方式,

    • 使用size字段表示实际容量
    size == 0 表示队空
    size == capacity 表示队满
    
    • 牺牲一个数组单元空间
    front == tail 表示队空
    (tail + 1) % capacity == front 表示队满
    (tail - front + capacity) % capacity 为实际容量
    

    第一种方式

    /**
     * 使用数组实现循环队列
     */
    public class LoopQueue<E> implements Queue<E> {
    
      /**
       * 数组容器
       */
      private Object[] data;
      /**
       * 队头和队尾指针
       */
      private int front, tail;
      /**
       * 队列实际容量
       */
      private int size;
    
      public LoopQueue(int capacity) {
        data = new Object[capacity];
        front = 0;
        tail = 0;
        size = 0;
      }
    
      public LoopQueue() {
        this(10);
      }
    
      private int capacity() {
        return data.length;
      }
    
      @Override
      public boolean isEmpty() {
        return size == 0;
      }
    
      @Override
      public void enqueue(E e) {
        int oldCapacity = capacity();
        if (size == oldCapacity) {
          //扩容1.5倍
          resize(oldCapacity + (oldCapacity >> 1));
        }
        data[tail] = e;
        tail = inc(tail + 1);
        size++;
      }
    
      @Override
      public E dequeue() {
        rangeCheck();
        E ret = front();
        data[front] = null;
        front = inc(front + 1);
        size--;
        int capacity = capacity();
        //在实际容量为总容量的4分之一时缩容
        if (size == (capacity >> 2) && (capacity >> 1) != 0) {
          //缩容2分之1
          resize(capacity >> 1);
        }
        return ret;
      }
    
      private E front() {
        return (E) data[front];
      }
    
      @Override
      public E peek() {
        rangeCheck();
        return front();
      }
    
      private void resize(int newCapacity) {
        Object[] newData = new Object[newCapacity];
        for (int i = 0; i < size; i++) {
          newData[i] = data[inc(i + front)];
        }
        data = newData;
        front = 0;
        tail = inc(size + front);
      }
    
      /**
       * 计算真正的索引
       *
       * @param index 待计算索引
       */
      private int inc(int index) {
        return index % data.length;
      }
    
      private void rangeCheck() {
        if (isEmpty()) {
          throw new IllegalArgumentException("queue is empty.");
        }
      }
    
      @Override
      public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size = %d , capacity = %d
    ", size, capacity()));
        res.append("front [");
        for (int i = 0; i < size; i++) {
          res.append(data[inc(i + front)]);
          if (i < size - 1) {
            res.append(", ");
          }
        }
        res.append("] tail");
        return res.toString();
      }
    }
    

    第二种方式

    /**
     * 使用数组实现循环队列
     */
    public class LoopQueueWithoutSize<E> implements Queue<E> {
    
      /**
       * 数组容器
       */
      private Object[] data;
      /**
       * 队头和队尾指针
       */
      private int front, tail;
    
      public LoopQueueWithoutSize(int capacity) {
        /**
         * 在需要的容量上增加一个容量
         */
        data = new Object[capacity + 1];
        front = 0;
        tail = 0;
      }
    
      public LoopQueueWithoutSize() {
        this(10);
      }
    
      private int capacity() {
        return data.length - 1;
      }
    
      @Override
      public boolean isEmpty() {
        return size() == 0;
      }
    
      private int size() {
        return inc(tail + data.length - front);
      }
    
      @Override
      public void enqueue(E e) {
        int oldCapacity = capacity();
        if (size() == oldCapacity) {
          //扩容1.5倍
          resize(oldCapacity + (oldCapacity >> 1));
        }
        data[tail] = e;
        tail = inc(tail + 1);
      }
    
      @Override
      public E dequeue() {
        rangeCheck();
        E ret = front();
        data[front] = null;
        front = inc(front + 1);
        //在实际容量为总容量的4分之一时缩容
        int capacity = capacity();
        if (size() == (capacity >> 2) && (capacity >> 1) != 0) {
          //缩容2分之1
          resize(capacity >> 1);
        }
        return ret;
      }
    
      private E front() {
        return (E) data[front];
      }
    
      @Override
      public E peek() {
        rangeCheck();
        return front();
      }
    
      private void resize(int newCapacity) {
        Object[] newData = new Object[newCapacity + 1];
        int size = size();
        for (int i = 0; i < size; i++) {
          newData[i] = data[inc(i + front)];
        }
        data = newData;
        front = 0;
        tail = inc(size + front);
      }
    
      /**
       * 计算真正的索引
       *
       * @param index 待计算索引
       */
      private int inc(int index) {
        return index % data.length;
      }
    
      private void rangeCheck() {
        if (isEmpty()) {
          throw new IllegalArgumentException("queue is empty.");
        }
      }
    
      @Override
      public String toString() {
        int size = size();
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size = %d , capacity = %d
    ", size, capacity()));
        res.append("front [");
        for (int i = 0; i < size; i++) {
          res.append(data[inc(i + front)]);
          if (i < size - 1) {
            res.append(", ");
          }
        }
        res.append("] tail");
        return res.toString();
      }
    }
    

    jdk也提供了一个数组实现的循环队列ArrayDeque,就是使用第二种方式实现的

    /**
     * Resizable-array implementation of the {@link Deque} interface.  Array
     * deques have no capacity restrictions; they grow as necessary to support
     * usage.  They are not thread-safe; in the absence of external
     * synchronization, they do not support concurrent access by multiple threads.
     * Null elements are prohibited.  This class is likely to be faster than
     * {@link Stack} when used as a stack, and faster than {@link LinkedList}
     * when used as a queue.
     *
     * <p>Most {@code ArrayDeque} operations run in amortized constant time.
     * Exceptions include
     * {@link #remove(Object) remove},
     * {@link #removeFirstOccurrence removeFirstOccurrence},
     * {@link #removeLastOccurrence removeLastOccurrence},
     * {@link #contains contains},
     * {@link #iterator iterator.remove()},
     * and the bulk operations, all of which run in linear time.
     *
     * <p>The iterators returned by this class's {@link #iterator() iterator}
     * method are <em>fail-fast</em>: If the deque is modified at any time after
     * the iterator is created, in any way except through the iterator's own
     * {@code remove} method, the iterator will generally throw a {@link
     * ConcurrentModificationException}.  Thus, in the face of concurrent
     * modification, the iterator fails quickly and cleanly, rather than risking
     * arbitrary, non-deterministic behavior at an undetermined time in the
     * future.
     *
     * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed
     * as it is, generally speaking, impossible to make any hard guarantees in the
     * presence of unsynchronized concurrent modification.  Fail-fast iterators
     * throw {@code ConcurrentModificationException} on a best-effort basis.
     * Therefore, it would be wrong to write a program that depended on this
     * exception for its correctness: <i>the fail-fast behavior of iterators
     * should be used only to detect bugs.</i>
     *
     * <p>This class and its iterator implement all of the
     * <em>optional</em> methods of the {@link Collection} and {@link
     * Iterator} interfaces.
     *
     * <p>This class is a member of the
     * <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
     * Java Collections Framework</a>.
     *
     * @author  Josh Bloch and Doug Lea
     * @param <E> the type of elements held in this deque
     * @since   1.6
     */
    public class ArrayDeque<E> extends AbstractCollection<E>
                               implements Deque<E>, Cloneable, Serializable {
       
        /**
         * 数组容器
         */
        transient Object[] elements;
    
        /**
         * 队头指针
         */
        transient int head;
    
        /**
         * 队尾指针
         */
        transient int tail;
        /**
         * 队列实际容量
         */
        public int size() {
            return sub(tail, head, elements.length);
        }
        /**
         * 计算实际容量
         */
        static final int sub(int i, int j, int modulus) {
            if ((i -= j) < 0) i += modulus;
            return i;
        }
        /**
         * 队列是否为空
         */
        public boolean isEmpty() {
            return head == tail;
        }
    }
    

    两种队列性能对比

    我们简单对比一下我们自己实现的ArrayQueue和LoopQueue

    import java.util.Random;
    
    public class Main {
    
      // 测试使用运行opCount个enqueue和dequeue操作所需要的时间,单位:秒
      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;
    
        Queue<Integer> arrayQueue = new ArrayQueue<>();
        double time1 = testQueue(arrayQueue, opCount);
        System.out.println("ArrayQueue, time: " + time1 + " s");
    
        Queue<Integer> loopQueue = new LoopQueue<>();
        double time2 = testQueue(loopQueue, opCount);
        System.out.println("LoopQueue, time: " + time2 + " s");
      }
    }
    

    运行结果为

    ArrayQueue, time: 0.520611 s
    LoopQueue, time: 0.0196162 s
    

    可以看到循环队列的优势还是很明显的。

    使用队列实现栈

    /**
     * 使用队列实现栈
     */
    public class QueueStack<E> implements Stack<E> {
    
      /**
       * 代理对象
       */
      private Queue<E> delegate;
    
      public QueueStack(int capacity) {
        delegate = new LoopQueue<>(capacity);
      }
    
      public QueueStack() {
        delegate = new LoopQueue<>();
      }
    
      @Override
      public boolean isEmpty() {
        return delegate.isEmpty();
      }
    
      @Override
      public void push(E e) {
        delegate.enqueue(e);
        while (delegate.peek() != e) {
          delegate.enqueue(delegate.dequeue());
        }
      }
    
      @Override
      public E pop() {
        return delegate.dequeue();
      }
    
      @Override
      public E peek() {
        return delegate.peek();
      }
    
      @Override
      public String toString() {
        return delegate.toString();
      }
    }
    

    入队时将队列中之前的元素重新入队,这样栈底就变栈顶了。

    使用栈实现队列

    /**
     * 使用栈实现队列
     */
    public class StackQueue<E> implements Queue<E> {
    
      /**
       * 代理对象
       */
      private Stack<E> delegateA;
      /**
       * 代理对象
       */
      private Stack<E> delegateB;
    
      public StackQueue() {
        delegateA = new LinkedStack<>();
        delegateB = new LinkedStack<>();
      }
    
      @Override
      public boolean isEmpty() {
        return delegateA.isEmpty() && delegateB.isEmpty();
      }
    
      @Override
      public void enqueue(E e) {
        delegateA.push(e);
      }
    
      @Override
      public E dequeue() {
        if (delegateB.isEmpty()) {
          fetchFromDelegateA();
        }
        return delegateB.pop();
      }
    
      @Override
      public E peek() {
        if (delegateB.isEmpty()) {
          fetchFromDelegateA();
        }
        return delegateB.peek();
      }
    
      private void fetchFromDelegateA() {
        while (!delegateA.isEmpty()) {
          delegateB.push(delegateA.pop());
        }
      }
    }
    

    使用两个栈实现队列,一个入栈,一个出栈。

  • 相关阅读:
    webservice时间类型XMLGregorianCalendar和Date的转换
    webservice中jaxws:server 和jaxws:endpoint的区别
    使用CXF开发JAX-WS类型的WebService
    使用TCP/IP Monitor监视Soap协议
    Webservice优缺点总结
    WebService两种调用方法
    DOS命令运行java文件,批量引用jar包
    eclipse创建的maven项目无法部署到tomcat
    图片翻转效果
    掷骰子效果
  • 原文地址:https://www.cnblogs.com/strongmore/p/14203303.html
Copyright © 2011-2022 走看看