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
可以看到循环队列的优势还是很明显的。
使用队列实现栈
/**
* 使用队列实现栈
*/
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());
}
}
}
使用两个栈实现队列,一个入栈,一个出栈。