數據結構之棧(Stack)

什麼是棧(Stack)

棧是一種遵循特定操做順序的線性數據結構, 遵循的順序是先進後出(FILO:First In Last Out)或者後進先出(LIFO:Last In First Out)
好比:
生活中,廚房裏的一摞盤子,你使用的時候會拿最上面的一個,最下面的那個最後使用。這就是FILO。當你想用第二個盤子時,先要拿起第一個,再拿出第二個,而後把第一個放到最上面。
棧的示意圖大體以下:
 
 

實現和操做概述

棧的主要操做有如下5種

Object push(Object element) : 向棧頂添加一個元素
Object pop() : 移除棧頂的元素並返回
Object peek() : 返回棧頂元素,但不刪除
boolean empty() : 判斷棧是否爲空,即棧頂是否爲空
int search(Object element) :查找element是否存在,存在則返回位置(棧頂爲1),不存在則返回-1。

棧的實現

能夠經過數組或鏈表均可以實現棧,下面都會詳細說明。
鏈表實現是本身寫的一個demo。
數組實現以java.util.Stack代碼作說明。java中的java.util.Stack類實現是經過數組的。
最後舉了一個常見的使用棧實現功能的例子: 符號匹配的問題

疑問

對比數組和鏈表的實現後,我的以爲用鏈表實現的更好,但JDK種使用的是數組實現。
這個 須要進一步瞭解,或者比較各個操做在運行時間及內存上的變化來分析。//TODO
鏈表實如今鏈表首部操做,數組實如今有效數組(數組中有效元素的部分,非數組最大容量)的末尾操做,pop、peek、empty、search操做感受相似(時間、內存消耗)。
下面作了個簡單表格:
鏈表實現的劣勢:需額外信息存儲引用,內存浪費;push時須要新建節點。
數組實現的劣勢:數組大小不足時,在push時須要擴容。擴容即須要從新分配更大空間並複製數據。
 
 
數組
鏈表
內存浪費
無浪費
有浪費:需存如額外引用信息
動態
非動態:大小沒法運行時隨意變更
動態的:能夠隨意增長或縮小
push操做
當數組大小超過期,須要擴容O(n)。
數組大小足夠時,直接push完成 O(1)
直接鏈表首部插入O(1). 但需新建節點
 
 

單鏈表實現

下面是簡易的demo。
經過單鏈表實現棧很容易實現,push()和pop()直接經過對鏈表首部的操做便可,時間複雜度都是O(1)。
下面demo很容理解,能夠看看註釋。須要瞭解  鏈表
public class StackTest<E> {
    public static void main(String[] args) {
        StackTest<Integer> stackTest = new StackTest<>();
        for (int i = 4; i > 0; i--) {
            System.out.println("push:" + stackTest.push(Integer.valueOf(i)).intValue());
        }
        System.out.println("peek:" + stackTest.peek());
        System.out.println("pop:" + stackTest.pop());
        System.out.println("isEmpty:" + stackTest.isEmpty());
        for (int i = 4; i > 0; i--) {
            System.out.println("search " + i + ":" + stackTest.search(Integer.valueOf(i)));
        }
    }    
    
    //棧頂定義
    StackNode<E> top;
    
    //節點定義:
    static class StackNode<E> {
        E data;
        StackNode<E> next;
  
        StackNode(E data, StackNode<E> next) {
            this.data = data;
            this.next = next;
        }
    }
 
    //向棧頂push一個元素,即向鏈表首部添加元素
    public E push(E data) {
        top = new StackNode<E>(data, top);
        return top.data;
    }
    
    //返回棧頂的值。即鏈表首部節點的值。
    public E peek() {
        if (isEmpty())
            throw new RuntimeException("fail,stack is null!");
        return top.data;
    }
    
    //從棧頂pop一個元素,即返回棧頂的值 並刪除鏈表第一個節點。
    public E pop() {
        E preTopData = peek();
        top = top.next;
        return preTopData;
    }
      
    //判空
    public boolean isEmpty() {
        return top == null;
    }
    
    //查找數據爲data的節點位置,棧頂爲1.沒找到返回-1.
    public int search(E data) {
        int position = 1;
        StackNode<E> currNode = top;
        
        while (currNode != null && !currNode.data.equals(data)) {
            position++;
            currNode = currNode.next;
        }
        if (currNode == null)
            position=-1;
        return position;
    }
}
push()和pop()操做後,效果爲:
打印log爲:
push:4
push:3
push:2
push:1
peek:1
pop:1
isEmpty:false
search 4:3
search 3:2
search 2:1
search 1:-1
 
 

棧的數組實現

如上所說,棧的數組實現方式以java.util.Stack類的代碼做爲說明。
Stack類是繼承了Vector類,兩個類方法都不少,截取了相關的代碼。
主要操做過程:定義一個數組(elementData)存放棧的數據;定義一個變量(elementCount)表示有效元素的個數或範圍,也就是棧中的元素;主要操做在有效元素部分的尾部,當push時超過數組大小,數組須要擴容。
大體示意圖以下:

push()操做

Stack<E>類:
public E push(E item) {
    addElement(item);
    return item;
}
Vector<E>類:
默認構造時,數組大小初始化爲10。 push()操做 即向數組最後一個有效元素位置後填入push的數據。當數組大小不足時,須要擴容。具體看下列註釋。
//數組變量定義
protected Object[] elementData;
//有效元素個數,在棧中即表示棧的個數
protected int elementCount;
//當數組溢出時,擴容 增長的大小。
protected int capacityIncrement;
//3種構造方式,默認構造方式的 數組大小初始化爲10.
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}
 
 
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}
 
 
public Vector() {
    this(10);
}
 
//增長元素
public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}

數組擴容

這種擴容方式,若是有相關需求能夠做爲參考。
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
 
 
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 
 
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
 
 
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
 

peek()操做

先看peek()是,因爲pop()會調用到。
Stack類:
棧有效個數爲elementCount(也是數組有效元素部分)。返回數組第elementCount個元素便可,下標爲elementCount-1。
public synchronized E peek() {
    int     len = size();
    if (len == 0)
        throw new EmptyStackException();
    return elementAt(len - 1);
}

Vector類:
html

public synchronized int size() {
    return elementCount;
}
 
public synchronized E elementAt(int index) {
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
    }
    return elementData(index);
}
 
@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

pop()操做

經過peek獲取棧頂元素並做爲返回值。再刪除棧頂元素。
部分代碼上面已有,不列出。
Stack類:
刪除數組中有效元素的最後一個,即下標爲elementCount-1
public synchronized E pop() {
    E       obj;
    int     len = size();
 
    obj = peek();
    removeElementAt(len - 1);
 
    return obj;
}
Vector類:
通用方法,數組中刪除某個元素,以後的元素要前移。
實際對於棧,傳入的index=elementCount-1。 因此j=0。不須要移動元素,是刪除的最後一個元素。
public synchronized void removeElementAt(int index) {
    modCount++;
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                 elementCount);
    }
    else if (index < 0) {
        throw new ArrayIndexOutOfBoundsException(index);
    }
    int j = elementCount - index - 1;
    if (j > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, j);
    }
    elementCount--;
    elementData[elementCount] = null; /* to let gc do its work */
}

search()操做

查找到後,返回位置。沒找到即返回-1。
這個位置相對棧頂,棧頂(1)即數組最後一個有效元素(下標爲 size-1)。若是是數組[i]是查找的元素,那麼相對棧頂位置即((size-1)- i )+1 = size - i。
Stack類中
public synchronized int search(Object o) {
    int i = lastIndexOf(o);
 
    if (i >= 0) {
        return size() - i;
    }
    return -1;
}
Vector類中:
public synchronized int lastIndexOf(Object o) {
    return lastIndexOf(o, elementCount-1);
}
 
public synchronized int lastIndexOf(Object o, int index) {
    if (index >= elementCount)
        throw new IndexOutOfBoundsException(index + " >= "+ elementCount);
 
    if (o == null) {
        for (int i = index; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = index; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

empty()操做

public boolean empty() {
    return size() == 0;
}

 

棧的使用

符號匹配問題

這裏只考慮3對符號,"{}[]()"。匹配規則如代碼中,如"{([])}」是符合的,"{([)]}"是不符合的。
思路很簡單:符合的狀況就是第一個右符合與前面最近的一個左符號是匹配的。
匹配到左符號,壓入堆棧;匹配到右符號,與棧頂比較,匹配即符合 pop出棧頂元素;當全部匹配完,棧爲空即符合的。
實現代碼以下:
public class StackTest<E> {
 
 
    public static void main(String[] args) {
        System.out.println(symbolMatch("{for(int i=0;i<10;i++)}"));
        System.out.println(symbolMatch("[5(3*2)+(2+2)]*(2+0)"));
        System.out.println(symbolMatch("([5(3*2)+(2+2))]*(2+0)"));
    }
    
    public static boolean symbolMatch(String expression) {
        final char CHAR_NULL = ' ';
        if (expression == null || expression.equals(""))
            throw new RuntimeException("expression is nothing or null");
        
        //StackTest<Character> stack = new StackTest<Character>();
        Stack<Character> stack = new Stack<Character>();
        char[] exps = expression.toCharArray();
        for (int i = 0; i < exps.length; i++) {
            char matchRight = CHAR_NULL;
            switch (exps[i]) {
                case '(':
                case '[':
                case '{':
                    stack.push(Character.valueOf(exps[i]));
                    break;
    
                case ')':
                    matchRight = '(';
                    break;
                case ']':
                    matchRight = '[';
                    break;
                case '}':
                    matchRight = '{';
                    break;
            }
            if(matchRight == CHAR_NULL)
                continue;
            if (stack.isEmpty())
                return false;
            if (stack.peek().charValue() == matchRight)
                stack.pop();
        }
        if (stack.isEmpty())
            return true;
        return false;
    }
}
輸出結果:
true
true
false
相關文章
相關標籤/搜索