數據結構--棧


  棧,和數組或鏈表一樣,也是一種用來存儲數據的線性結構,但不同的是,棧對數據的存取有着限制,它遵循着先進后出或后進先出的原則。怎么理解呢?想一想收件箱中的郵件。打開收件箱,郵件是按時間順序從晚到早時進行排列的,第一封郵件時間最晚,最后一封郵件時間最早,郵件來的越早,它越在收件箱的底部,郵件來的越晚,它越在收件箱的頂部。看郵件的時候,先看第一封郵件,看的是來的最晚的郵件。如果不重要就刪除了,這就是后進先出,最晚的郵件最先刪除。看了好久,終於快看完了,先進后出。此時,又收到一封郵件,它又放到收件箱的頂端,你發現,無論是添加還是刪除,都是在收件箱的頂部操作。如果把收件箱看作棧,你會出現棧的一些特征,棧中元素的排列順序,正好和它們的添加時間相反,添加的越早,越在棧的下面,添加的越晚,越在棧的上面。添加元素相當於把原有元素向下壓,刪除元素相當於下面的元素向上頂,把元素彈出去。添加或刪除,都在棧的一端操作,棧的頂部操作,而不是隨意的操作棧中的元素,如果想獲取棧底的元素,只能把它上面的元素都彈出來。元素稱為入棧或壓棧,刪除元素稱為出棧或彈棧。

  棧是一種抽象的概念,要怎么實現它的呢?實際上,只要保證先進后出或后進先出的原則,用什么實現都可以,數組,鏈表,甚至隊列,都沒有問題。

  數組實現  

  使用數組,怎么保證先進后出或后進先出呢?在數組的尾部進行添加或刪除元素就可以了。添加第一個元素放到數組的0位置,添加第二個元素放到數組的1位置 ,刪除的時候,先刪除第1個位置的元素,再刪第0個位置上的元素。  數組的尾部進行操作,保證了后進先出。數組尾部操作也簡單,它不會影響數組內的其它元素。根據描述,也可以知道實現棧的類的屬性,一個是數組的引用,一個是追蹤元素的索引。

構造函數就是要創建數組,進行初始化棧。創建數組要指明數組的長度,長度具體是多少,可以讓使用者指定,也可以設一個默認的長度。

public class ArrayStack<T> {
   private T[] stack; // 實現棧的底層數組
   private int topIndex; // 記錄棧中或數組中最后一個元素的位置
   private static final int DEFAULT_CAPACITY = 50; // 默認數組的大小

   public ArrayStack() {
      this(DEFAULT_CAPACITY);
   }

   public ArrayStack(int initialCapacity) {
      stack = (T[])new Object[initialCapacity];
      topIndex = 0;
   }
}

  添加元素(入棧),稱為push,就是向數組的尾部添加元素,如下圖所示

   就是數組topIndex 的位置處設為添加的元素,同時topIndex加1。

public void push(String elem) {
    stack[topIndex] = elem;
    topIndex++;
}

  刪除元素或出棧,稱為pop。就是topInex--,獲取到topIndex處的元素,然后把數組這個位置設為null。

public T pop(){
   topIndex--;
   String item = stack[topIndex];
   stack[topIndex] = null;

   return item;
}

  當然,如果棧為空的話,是不能執行刪除操作的,如果執行刪除操作,就報錯。這時還需要isEmpty()的方法,同時在pop()中調用

public boolean isEmpty() {
   return topIndex == 0;
}
public T pop(){
   if (isEmpty()){
      throw new RuntimeErrorException("棧為空");
   }
   topIndex--;
   T item = stack[topIndex];
   stack[topIndex] = null;

   return item;
}

  除了主要的push和pop方法外,棧還有其它幾個基本方法。peek() 方法, 看一下棧的頂部元素是什么,它只需要獲取元素,而不用移動topIndex。和pop()方法一樣,如果棧為空,peek()方法報錯

public T peek() {
   if(isEmpty()){
      throw  new RuntimeErrorException("棧為空");
   }
   return stack[topIndex - 1];
}

  size()方法,棧中有多少個元素,直接返回topIndex 就可以了。

public int size() {
   return topIndex;
}

  clear(), 清空棧,簡單點,就直接讓topIndex = 0; 復雜一點,就是調用pop() 方法。

public void clear() {
   topIndex = 0;
}

   數組實現有一個問題,就是是數組的長度是固定的,如果不停地添加元素,數組會滿的。這個時候,就要擴容。擴容的實現方式是重新創建一個大數組,把stack數組中的元素,復制到新的數組中,然后讓stack指向這個新數組

public void ensureCapcity(){
   T[] bigArray = (T[])new Object[stack.length * 2];
   for(int i = 0; i < stack.length; i++){
      bigArray[i] = stack[i];
   }

   stack = bigArray;
}

  當不斷刪除元素的時候,數組就需要縮容。縮容和擴容的原理一樣,只不過是生成一個小的數組,這時可以把ensureCapcity()方法接受一個參數,擴容和縮容就可以使用一個方法了,把方法名改成resize()

private void resize(int max) { 
        T[] tempArray = (T[])new Object[max];
        for (int i = 0; i < N; i++)
            tempArray[i] = stack[i];
        stack = temp;
    }

   push() 方法,就要選判斷數組是不是還有剩余空間,如果沒有就擴容,調用resize()方法,擴多大呢?一般是原數組大小的兩倍。

public void push(String elem) {
    if (topIndex == stack.length)
        resize(2 * stack.length);
    stack[topIndex] = item;
    topIndex++;
}

  縮容,就是在斷刪除元素的時候,

public T pop(){
    if (isEmpty()){
       throw new RuntimeErrorException("棧為空");
    }

    topIndex--;
    T item = stack[topIndex];
    stack[topIndex] = null;

    if (topIndex > 0 && topIndex== stack.length / 4) {
        resize(stack.length / 2);
    }

    return item;
 }

  迭代方法的實現

import java.util.Iterator;

public class ArrayStack<T> implements Iterable<T> {

    @Override
    public Iterator<T> iterator() {
        return new ReverseArrayIterator();
    }

    private class ReverseArrayIterator implements Iterator<T> {
        int i = topIndex;
        @Override
        public boolean hasNext() {
            return i > 0;
        }
        @Override
        public T next() {
            return stack[--i];
        }
    }
}

   鏈表實現

  鏈表需要節點和頭指針,棧需要記錄元素的個數,所能實現棧的鏈表類需要三個屬性,

public class LinkedListStack<T>{
    private class Node {
        T item; // 棧中元素
        Node next;
    }

    private Node topNode;
    private int N;
}

  入棧和出棧的操作可以放在鏈表的頭部,從頭部插入節點,頭指針指向最近插入的節點,從頭部刪除節點,最近插入的節點補刪除了,后進先出。鏈表的頭部插入節點,創建新節點newNode, 它的next指向原來的頭指針,再把新節點newNode賦值給頭指針

public void push(T item) {
    Node newNode = new Node();
    newNode.item = item;
    newNode.next = topNode;
    topNode =newNode;
    N++;
}

  出棧,從鏈表的頭部刪除節點,直接讓topNode = topNode.next, topNode 指向鏈表的第二個節點,第一個節點也就引用不到了,也就刪除了。

public T pop() {
    if(isEmpty()){
        throw new RuntimeException("棧為空");
    }
    
    T item = topNode.item;
    first = topNode.next;
    N--;
    return item;
}

  其它幾個簡單方法

public boolean isEmpty() {
    return topNode == null;
}

public int size() {
    return N;
}

public void clear() {
    topNode = null;
}

public T peek(){
    if(isEmpty()){
        throw new RuntimeException("棧為空");
    }
    return topNode.item;
}

  棧的迭代,一個是next()方法,返回棧中的元素,可以聲明一個變量temp,先指向第一個元素,每調用一次next方法,就讓這個變量,指向下一個節點,temp=temp.next 。一個是hasNext方法,棧中還有沒有元素,可以判斷,temp是否等於null,如果等於null,就表明到了鏈表的尾部,沒有元素了,反之,則有元素。

import java.util.Iterator;

public class LinkedListStack<T> implements Iterable<T>{

    @Override
    public Iterator<T> iterator() {
        return new StackIterator();
    }
    
    private class StackIterator implements Iterator<T> {

        Node temp = topNode;

        @Override
        public boolean hasNext() {
            return temp != null;
        }

        @Override
        public T next() {
            T item = temp.item;
            temp = temp.next;
            return item;
        }    
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM