java使用数组和链表实现栈和队列

前言

栈(Stack)是一种后进先出的数据结构,仅允许在栈顶插入、删除、读取。队列(Queue)是一种先进先出的数据结构,队头读取、删除,队尾插入。
java使用数组和链表实现栈和队列

使用数组实现栈

使用到的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),这种情况我们可以使用循环队列来优化,
java使用数组和链表实现栈和队列

使用两个指针表示队头(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

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

上一篇:jQuery中delegate()方法详解


下一篇:CoreLocation