棧: 限定僅在表尾進行插入和刪除操做的線性表;java
棧也是線性表,只是對錶中元素的插入和刪除位置作了限定,所以咱們很容易想到利用一維數組實現棧的存儲結構。Java中的Stack類繼承自Vector,就是用數組實現。數組
Stack.javabash
public class Stack<E> extends Vector<E> {
public Stack() {
}
public E push(E item) {
addElement(item);
return item;
}
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
public boolean empty() {
return size() == 0;
}
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
private static final long serialVersionUID = 1224463164541339165L;
}複製代碼
兩棧共享存儲空間數據結構
若是咱們有兩個相同類型的棧,咱們爲他們各自開闢了數組空間,極有可能第一個棧已經滿了,再進棧就溢出了,而另外一個棧還有不少存儲空間空閒。這時,咱們能夠充分利用順序棧的單向延伸的特性,使用一個數組來存儲兩個棧,讓一個棧的棧底爲數組的始端,另外一個棧的棧底爲數組的末端,每一個棧從各自的端點向中間延伸。ui
ShareStack.javathis
/** * Created by engineer on 2017/10/22. */
public class ShareStack<T> {
private Object[] element; //存放元素的數組
private int stackSize; // 棧大小
private int top1; //棧1的棧頂指針
private int top2; //棧2的棧頂指針
/** * 初始化棧 * @param size */
public ShareStack(int size){
element = new Object[size];
stackSize = size;
top1 = -1;
top2 = stackSize;
}
/** * 壓棧 * @param i 第幾個棧 * @param o 入棧元素 * @return */
public boolean push(int i , Object o){
if(top1 == top2 - 1)
throw new RuntimeException("棧滿!");
else if(i == 1){
top1++;
element[top1] = o;
}else if(i == 2){
top2--;
element[top2] = o;
}else
throw new RuntimeException("輸入錯誤!");
return true;
}
/** * 出棧 * @param i * @return */
@SuppressWarnings("unchecked")
public T pop(int i){
if(i == 1){
if(top1 == -1)
throw new RuntimeException("棧1爲空");
return (T)element[top1--];
} else if(i == 2){
if(top2 == stackSize)
throw new RuntimeException("棧2爲空");
return (T)element[top2++];
} else
throw new RuntimeException("輸入錯誤!");
}
/** * 獲取棧頂元素 * @param i * @return */
@SuppressWarnings("unchecked")
public T get(int i){
if(i == 1){
if(top1 == -1)
throw new RuntimeException("棧1爲空");
return (T)element[top1];
} else if(i == 2){
if(top2 == stackSize)
throw new RuntimeException("棧2爲空");
return (T)element[top2];
} else
throw new RuntimeException("輸入錯誤!");
}
/** * 判斷棧是否爲空 * @param i * @return */
public boolean isEmpty(int i){
if(i == 1){
if(top1 == -1)
return true;
else
return false;
} else if(i == 2){
if(top2 == stackSize)
return true;
else
return false;
} else
throw new RuntimeException("輸入錯誤!");
}
}複製代碼
固然,考慮到數組須要在初始化的時候限定大小,同時也要考慮擴容的問題。所以棧也可使用鏈表來實現;這個後面一塊兒討論,這裏就不展開來講了。spa
棧這種數據結構,很是實用;Android中Activity的回退棧就是最好的例子,正常模式下,咱們經過startActivity就是將一個Activity壓入了回退棧,finish()方法就是從回退棧裏彈出最頂部的Activity;固然,實際流程有不少別的操做,這裏也只是大致流程;遞歸思想也是利用了棧這種結構。線程
隊列: 只容許在一端進行插入操做、而在另外一端進行刪除操做的線性表。3d
使用數組實現隊列的存儲結構時,爲了不每次從隊頭刪除元素時,移動後面的每一個元素,加入了front和rear兩個指針,分別指向隊頭和隊尾;這樣每次從隊頭刪除元素時,移動front指針便可,而沒必要移動大量的元素,可是這樣勢必會形成假溢出的問題,存儲空間得不到充分的利用,所以須要採用循環隊列的方式實現了隊列的順序存儲結構。指針
假定在循環隊列中,QueueSize爲循環隊列大小,即數組長度,則有如下結論:
總的來講,採用順序存儲結構,仍是須要考慮容量的問題。所以,在咱們沒法預估隊列長度的狀況下,須要關注鏈式存儲結構。
在上文中咱們已經說過,LinkList實現了Deque接口,所以它就是用鏈表實現的隊列。這裏簡單分析一下入隊push和出隊pop操做的實現。
LinkedList-add 隊列入隊
public boolean add(E e) {
linkLast(e);
return true;
}
/** * Links e as last element. */
void linkLast(E e) {
final Node<E> l = last;
//建立新的結點,其前驅指向last,後繼爲null
final Node<E> newNode = new Node<>(l, e, null);
//last 指針指向新的結點
last = newNode;
if (l == null)
first = newNode; //若是鏈表爲空,frist指針指向新的結點
else
l.next = newNode; //鏈表不爲空,新的結點鏈接到原來最後一個結點以後
size++; //鏈表長度+1
modCount++;
}複製代碼
LinkList是一個雙向鏈表,這裏first是執行第一個結點的指針,last是指向最後一個結點指針。
LinkList-pop 隊列出隊
public E pop() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//獲取要刪除結點的值
final E element = f.item;
//獲得f的下一個結點,也就是第二個結點
final Node<E> next = f.next;
// f 釋放
f.item = null;
f.next = null; // help GC
// first 指針指向f的下個結點,
first = next;
// f 後面已經沒有結點了
if (next == null)
last = null;
else
next.prev = null; // 第二個結點(也就是如今的第一個結點)前驅爲null,由於LinkList 是雙端鏈表,非循環。
size--;
modCount++;
return element;
}複製代碼
這裏就是一個典型的單鏈表刪除頭結點的實現。至此,咱們已經掌握了棧和隊列這兩種數據結構各自的特色;下面再來看看Java官方提供的關於棧和隊列的實現。
這裏主要說一下Deque這個類。
/** * A linear collection that supports element insertion and removal at * both ends. The name <i>deque</i> is short for "double ended queue" * and is usually pronounced "deck". Most {@code Deque} * implementations place no fixed limits on the number of elements * they may contain, but this interface supports capacity-restricted * deques as well as those with no fixed size limit. * / public interface Deque<E> extends Queue<E> { void addFirst(E var1); void addLast(E var1); boolean offerFirst(E var1); boolean offerLast(E var1); E removeFirst(); E removeLast(); E pollFirst(); E pollLast(); E getFirst(); E getLast(); E peekFirst(); E peekLast(); boolean add(E var1); boolean offer(E var1); E remove(); E poll(); E element(); E peek(); void push(E var1); E pop(); ........ }複製代碼
Deque接口是「double ended queue」的縮寫(一般讀做「deck」),即雙端隊列,支持在線性表的兩端插入和刪除元素,繼承Queue接口。大多數的實現對元素的數量沒有限制,但這個接口既支持有容量限制的deque,也支持沒有固定大小限制的。
咱們知道Queue接口定義了隊列的操做集合,而Deque接口又在其基礎上擴展,定義了在雙端進行插入刪除的操做。所以,咱們很能夠認爲,Deque接口既能夠當作隊列,也能夠當作棧。
所以,回過頭來,咱們能夠發現LinkList以鏈表結構,同時實現了隊列和棧。前面已經分析了LinkList做爲一個隊列的操做。下面咱們能夠看看,他又是如何實現鏈式結構實現隊列的。
public void addLast(E e) {
linkLast(e);
}複製代碼
能夠看到,對於入棧操做和隊列樣,都是在鏈表最後插入元素,和隊列同樣使用了linkLast()方法。
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}複製代碼
出棧一樣是用了unlinkLast 方法,只不過出棧的元素是last。而不是隊列中的first。
ArrayDeque 用一個動態數組實現了棧和隊列所需的全部操做。
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);
System.arraycopy(elements, 0, a, r, p);
elements = a;
head = 0;
tail = n;
}複製代碼
這裏能夠看到,不管是頭部仍是尾部添加新元素,當須要擴容時,會直接變化爲原來的2倍。同時須要複製並移動大量的元素。
public E pollFirst() {
final Object[] elements = this.elements;
final int h = head;
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// Element is null if deque empty
if (result != null) {
elements[h] = null; // Must null out slot
head = (h + 1) & (elements.length - 1);
}
return result;
}
public E pollLast() {
final Object[] elements = this.elements;
final int t = (tail - 1) & (elements.length - 1);
@SuppressWarnings("unchecked")
E result = (E) elements[t];
if (result != null) {
elements[t] = null;
tail = t;
}
return result;
}複製代碼
從頭部和尾部刪除(獲取)元素,就比較方便了,修改head和tail位置便可。head是當前數組中第一個元素的位置,tail是數組中第一個空的位置。
/** * A {@link Deque} that additionally supports blocking operations that wait * for the deque to become non-empty when retrieving an element, and wait for * space to become available in the deque when storing an element. * / public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> { }複製代碼
關於Deque最後一點,BlockingDeque 在Deque 基礎上又實現了阻塞的功能,當棧或隊列爲空時,不容許出棧或出隊列,會保持阻塞,直到有可出棧元素出現;同理,隊列滿時,不容許入隊,除非有元素出棧騰出了空間。經常使用的具體實現類是LinkedBlockingDeque,使用鏈式結構實現了他的阻塞功能。Android中你們很是熟悉的AsyncTask 內部的線程池隊列,就是使用LinkedBlockingDeque實現,長度爲128,保證了AsyncTask的串行執行。
這裏比較一下能夠發現,對於棧和隊列這兩種特殊的數據結構,因爲獲取(查找)元素的位置已經被限定,所以採用順序存儲結構並無很是大的優點,反而是在添加元素因爲數組容量的問題還會帶來額外的消耗;所以,在沒法預先知道數據容量的狀況下,使用鏈式結構實現棧和隊列應該是更好的選擇。
好了,棧和隊列就先到這裏了。