1. 程式人生 > 實用技巧 >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\n", 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\n", 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

可以看到迴圈佇列的優勢還是很明顯的。