天天看點

資料結構:棧棧的定義棧的資料結構棧的應用

資料結構:棧

  • 棧的定義
  • 棧的資料結構
    • 棧的順序存儲結構
    • 棧的鍊式存儲結構
    • JAVA中的棧
  • 棧的應用

棧的定義

棧是一種隻能在表尾進行插入或者删除操作的線性表。

允許插入和删除的一端叫棧頂,另一端叫棧底,無任何元素叫空棧(空表),是一種先入後出的線性表。

棧的插入操作叫進棧,也有叫壓棧、入棧。(如圖)

資料結構:棧棧的定義棧的資料結構棧的應用

棧的删除操作叫出棧,也有叫彈棧。(如圖)

資料結構:棧棧的定義棧的資料結構棧的應用

棧的資料結構

棧的順序存儲結構

棧的順序存儲結構也是線性表的存儲結構,可以用數組實作。

資料結構:棧棧的定義棧的資料結構棧的應用
public class ArrayStack<T> {
    private int DEFAULT_SIZE = 5;//預設大小
    private Object[] objs;
    private int topIndex = 0;//用來紀錄目前棧頂的下标

    public ArrayStack(int DEFAULT_SIZE) {
        this.DEFAULT_SIZE = DEFAULT_SIZE;
        objs = new Object[DEFAULT_SIZE];
    }

    public ArrayStack() {
        objs = new Object[DEFAULT_SIZE];
    }

    /**
     * 進棧
     * @param t 進棧元素
     */
    public void push(T t) {
        //表示目前數組已經沒有多餘空間(要擴容)
        if (topIndex > DEFAULT_SIZE - 1) {
            objs = AddSize(objs, DEFAULT_SIZE / 2);
        }
        objs[topIndex] = t;
        topIndex++;
    }

    /**
     * 出棧
     * @return true 表示出棧成功,false表示棧為空,出棧失敗
     */
    public boolean pop() {
        if (topIndex < 0) {
            return false;
        }
        //清空棧頂
        objs[topIndex--] = null;
        return true;
    }

    /**
     * @param objs 表示要擴容的數組
     * @param size 表示要數組要增加多大
     * @return 傳回擴容後的新數組
     */
    private Object[] AddSize(Object[] objs, int size) {
        int newSize = objs.length + size;
        Object[] newObj = new Object[newSize];
        System.arraycopy(objs, 0, newObj, 0, objs.length);
        return newObj;
    }
}
           
優點 缺點
存取定位友善 需要提前配置設定空間,當空間不足時則需要擴容,影響性能,如果不擴容就會造成溢出

棧的鍊式存儲結構

棧的鍊式存儲結構可以用線性表的鍊式存儲結構來實作。

資料結構:棧棧的定義棧的資料結構棧的應用
public class LinkStack<T> {

    //表示棧的大小
    private int size;
    //棧頂元素
    private Node top;

    private class Node<T> {

        private Node next;//存放後繼節點
        private T data;//資料域

        public Node(T data, Node next) {
            this.next = next;
            this.data = data;
        }

        public Node() {
        }

    }

    /**
     * 入棧
     * @param data
     */
    public void push(T data) {
        top = new Node<>(data, top);
        size++;
    }

    /**
     * 出棧
     * @return
     */
    public T pop() {
        if (top == null) {
            return null;
        }
        Node<T> node = top;
        top = top.next;
        node.next = null;
        size--;
        return node.data;
    }
}

           
優點 缺點
不用提前申請空間,動态配置設定存儲空間 因為多了存放指針的操作,會額外增加記憶體

總結:如果棧的元素數量固定,建議用鍊式存儲結構,而元素數量變化不定,則建議用鍊式存儲結構。

JAVA中的棧

JDK封裝了Stack的類。位于java.util的包名下。

package java.util;
public class Stack<E> extends Vector<E> {
    /**
     * 無參的構造函數
     */
    public Stack() {
    }

    /*
     * 壓棧
     */
    public E push() {
    	//壓棧
        addElement(item);
		//傳回壓入棧中的元素
        return item;
    }

    /**
     * 出棧
     */
    public synchronized E pop() {
        E       obj;
        int     len = size();
		//擷取棧頂的元素
        obj = peek();
        //把棧頂元素置為空,并把可用數組長度減一
        removeElementAt(len - 1);

        return obj;
    }

}
           

主要看壓棧的方法 addElement:

//這是個線程安全的方法
 public synchronized void addElement(E obj) {
        modCount++;
        //擴容
        ensureCapacityHelper(elementCount + 1);
        //壓棧給棧頂指派 lementData為vector的object[]數組
        elementData[elementCount++] = obj;
    }
           

比較複雜的是擴容的方法ensureCapacityHelper:

private void ensureCapacityHelper(int minCapacity) {
        // 如果目前棧中的元素再加一比elementData的數組長度大,則需要擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //傳入實際棧的數量
     private void grow(int minCapacity) {
        // 先把舊的數組長度指派給oldCapacity 
        int oldCapacity = elementData.length;
        //capacityIncrement 為自定義要增加的長度(擴容因子),沒有設定則預設為0,stack沒有設定這個變量,這裡數組的長度為newCapacity =oldCapacity +oldCapacity .
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
         //如果新産生的數組長度小于實際需要的數組長度,則使用實際長度。                              
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
         //如果新産生的數組長度大于 Integer.MAX_VALUE - 8  則等于Integer.MAX_VALUE 或	者Integer.MAX_VALUE - 8
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
            //真正在這裡擴容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
   
           

Arrays.copyOf方法:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
     	//拿到數組類型,并建立newLength長度的數組。
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
            //把original的數組從0開始,複制到名為copy,下标為0開始的新數組上,到original數組長度跟newLength的最小值的地方(一般都是original.length)。
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        //傳回帶有中繼資料的新數組
        return copy;
    }
    
           

出棧方法 pop()調用了peek()與removeElementAt()

先看peek()

public synchronized E peek() {
 		//擷取棧中元素數量
        int     len = size();

        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    } 
    
    //直接傳回數組下标為index的元素
	public synchronized E elementAt(int index) {
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " + 	elementCount);
        }
        return elementData(index);
    }
           

再看出棧的主要方法removeElementAt:

public synchronized void removeElementAt(int index) {
 		//計數器加1
        modCount++;
        //如果下标大于數組總長度抛異常
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
         //如果下标為負數抛異常
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        //擷取需要複制數組元素的個數(用可用的數組減去棧頂的下标再減一,減一是因為下标是從0開始)
        int j = elementCount - index - 1;
        if (j > 0) {
        	//重新構造數組,這裡主要是移除非棧頂的元素,棧沒有用到。
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        //有效數量鍵1
        elementCount--;
        //清空數組中最後一個元素清空
        elementData[elementCount] = null; /* to let gc do its work */
    }

           

可以看到java提供的棧stack用了線性表的順序存儲結構。

java提供的stack可以直接使用,不用關心棧溢出問題,調用stack的push()、pop(),就可以進行入棧出棧操作。

但有一個缺點是當Stack快速入棧多個元素後再恢複回來,在元素不變的前提下,其實elementCount數組的長度會增加,這個數組會一直占有着記憶體,造成記憶體浪費。

棧的應用

  • Android中的atcivity棧。
  • 遞歸函數(如斐波那契數列的實作)。
  • 四則運算中的中綴表達式與字尾表達式的計算。
參考:大話資料結構

下一節 資料結構:隊列

繼續閱讀