java使用陣列和連結串列實現棧和佇列
阿新 • • 發佈:2020-12-28
前言
棧(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
可以看到迴圈佇列的優勢還是很明顯的。