棧又稱堆棧,是限制在表的一端進行插入和刪除運算的線性表。
表中進行插入、刪除操做的一端稱爲棧頂(top)。
棧頂保存的元素稱爲棧頂元素。
表的另外一端稱爲棧底(bottom)。
當棧中沒有元素時稱爲空棧。
向一個棧中插入元素稱爲進棧或入棧或壓棧(push)。插入的元素是當前最新的。
從一個棧中刪除元素稱爲出棧或退棧或彈棧(pop)。刪除的元素是當前最新的。
因爲棧的插入和刪除僅在棧頂進行,後進棧的元素一定先出棧,因此把堆棧稱爲後進先出表(Last In First Out,LIFO)。
當棧滿時進棧運算稱爲上溢;當棧空時出棧運算稱爲下溢。java
基本操做和ADT定義以下:算法
ADT Stack{ 數據對象:D={ai|ai∈element,i=1,2,3……,n,n≥0} 數據關係:R={<ai-1,ai>|ai-1,ai∈D,約定an端爲棧頂,a1端爲棧底} 基本操做: init(int s); //構造一個空棧s clear(); //清空棧 isEmpty(); //判斷堆棧是否爲空 isFull(); //判斷棧是否滿。 peek(); //獲取棧頂元素但不出棧 push(Object o); //元素o入棧 pop(); //棧頂元素出棧 getSize(); //獲取元素個數。 }ADT Stack
堆棧的存儲結構有順序存儲結構和鏈式存儲結構兩種。
在順序存儲結構中要考慮堆棧的上溢;在鏈式存儲結構中要考慮堆棧的下溢。
堆棧上溢是一種出錯狀態,應該設法避免它;堆棧下溢多是正常現象,一般下溢用來做爲程序控制轉移的條件。
就線性表而言,實現棧的方法有不少,這裏着重介紹順序棧(arrary based stack)和鏈式棧(linked stack)。數組
順序棧(arrary based stack)的實現從本質上講,就是順序線性表實現的簡化。
若是用數組來實現,惟一要肯定的是使用哪一端來表示棧頂。
若是使用數組的其實位置來做爲棧頂,那麼在刪除和插入的時候會有很大的時間消耗,由於平移元素。
若是使用素組的尾端來做爲棧頂,那麼就不須要移動元素了。函數
堆棧的運算主要考慮入棧和出棧的算法。
入棧時須要考慮的操做步驟是堆棧初始化,而後判斷堆棧是否爲滿,若是不滿,則能夠插入元素。
出棧時,須要考慮的步驟是判斷堆棧是否爲空,若是不空,刪除元素,出棧以前,保存棧頂元素。this
堆棧順序存儲時,爲避免上溢,須要首先分配較大空間,但這容易形成大量的空間浪費。因此當使用兩個棧時,能夠將兩個棧的棧底設在向量空間的兩端,讓兩個棧各自向中間靠攏,使空間得以共享。邏輯圖以下:spa
具體實現方法
利用一個數組來存儲兩個堆棧,每一個棧各自的斷點向中間延伸。.net
利用數組實現一個順序棧。設計
/** * 使用數組實現堆棧 * @author 星漢 * */ public class ListStack { private Object[] elements;//數組容器 private int size;//元素個數 /** * 默認大小爲32的堆棧 */ public ListStack() { this.elements=new Object[32]; this.size=0; } /** * 清空棧 * @return 成功返回true */ public boolean clear() { for(int i=size-1;i>=0;i--,size--) { elements[i]=null; } return size<=0; } /** * 判斷棧是否爲空 * @return 爲空返回true */ public boolean isEmpty() { return size==0; } /** * 判斷是否滿棧 * @return 棧滿返回true */ public boolean isFull() { return size==elements.length; } /** * 獲取棧頂元素,但不刪除棧頂元素 * @return object 棧頂元素 */ public Object peek() { if(size!=0) { return elements[size-1]; } return null; } /** * 入棧 * @param o 入棧元素 */ public void push(Object o) { if(size<elements.length) { elements[size]=o; size++; }else { System.out.println("上溢"); } } /** * 出棧 * @return 棧頂元素 */ public Object pop() { if(size>0) { Object tmp = elements[size-1]; elements[size-1]=null; size--; return tmp; }else { System.out.println("下溢"); return null; } } /** * 返回棧中元素個數 * @return */ public int getSize() { return size; } }
堆棧的鏈式存儲稱爲鏈棧,即採用鏈表做爲存儲結構實現的棧。鏈式棧的基本運算同順序棧。它是對鏈表實現的簡單化。
使用單向鏈表實現的棧只能對錶頭進行操做,由於不能反向查找。指針
實現順序棧和鏈式棧都須要常數時間。
順序堆棧初始時,須要說明一個固定的長度,當堆棧不夠滿時,會形成空間浪費。
鏈式棧的長度可變,不須要預先設定,相對比較節省空間,可是每一個結點中設置一個指針域會產生結構開銷。
順序棧能夠實現共享空間。
鏈式棧通常不用。code
堆棧的應用例子比較多,但比較典型的是數制的轉換、表達式的計算、轉換問題和遞歸問題。
數制轉換的基本原理:
N mod d的值是餘數,餘數做爲轉化後的值,用N div d的商再做爲N的值,再求餘數,依次類推,直到商數爲零。最後所得的餘數反向輸出,就是須要的結果。
由上述原理能夠看出,這個過程剛好知足棧的運算規則:先進後出。因此可使用堆棧實現數制的轉換。
/** * 數制轉換 * @author 星漢 * */ public class TransportNum { public static void main(String[] args) { ListStack ls=new ListStack();//利用上面實現的棧 int n=20;//未知數 int remainder=0;//餘數 int d=2;//進制數 while(n!=0) { remainder=n%d; n=n/d; ls.push(remainder);//將餘數入棧 } while(!ls.isEmpty()){ System.out.print(ls.pop()); } } }
表達式通常有中綴表達式、後綴表達式和前綴表達式3種表現形式。現實生活中使用的是中綴表達式,計算機內存儲表達式時通常採用後綴或前綴表達式。
一個表達式一般由操做數、運算符及分隔符所構成。
中綴表達式就是將運算符放在操做數中間,例如:a+b*c
因爲運算符有優先級,因此在計算機內部使用中綴表達式是很是不方便的。爲了方便處理起見,通常須要將中綴表達式利用堆棧轉換成爲計算機比較容易識別的前綴表達式或者後綴表達式。
例如:前綴表達式:+a*bc 後綴表達式:abc*+
其轉換過程按照優先級轉換,運算符的優先級順序表以下圖:
以中綴表達式a/(b-c)爲例,演示一下中綴表達式轉換爲前綴表達式的具體步驟:
第一步:先處理優先級高的,括號內將(b-c)轉換爲(-bc)。
第二步:將除號進行處理爲/a,整個表達式爲/a(-bc)。
第三步:消除括號爲/a-bc,就是將中綴表達式轉爲前綴表達式。
利用堆棧處理中綴表達式的步驟以下:
第一步:設置兩個堆棧,一個操做數堆棧和一個運算符堆棧。
第二步:初始時爲空,讀取表達式時,只要督導操做數,將操做數壓入操做數棧。
第三步:當讀到運算符時將新運算符和棧頂運算符的優先級比較,若是新運算符的優先級高於棧頂運算符的優先級,將新運算符壓入運算符堆棧;不然取出棧頂的運算符,同時取出操做數堆棧中的兩個操做數進行計算,計算結果壓入操做數堆棧。
中綴表達式的計算須要使用兩個堆棧,而且計算比較頻繁,然後綴或前綴表達式的實現只須要一個堆棧。
將中綴表達式轉換爲後綴表達式,轉換原則以下:
第一:從左至右讀取一箇中綴表達式。
第二:若讀取的是操做數,則直接輸出。
第三:若讀取的是運算符,分三種狀況:
1.該運算符爲左括號「(」,則直接存入堆棧。
2.該運算符爲右括號「)」,則輸出堆棧的運算符,直接取出左括號爲止。
3.該運算符爲費括號運算符,則與堆棧頂端的運算符作優先權比較,若堆棧頂端運算符高或者相等,則直接存入棧;若較棧頂端的運算符低,則輸出堆棧中的運算符。
第四:當表達式已經讀取完成,而堆棧中尚有運算符時,則依次序取出運算符,知道堆棧爲空,由此獲得的結果就是中綴表達式轉換成的後綴表達式。
遞歸問題其實是程序或函數重複調用本身,並傳入不一樣的變量來執行一種程序。
遞歸程序編寫雖然簡單,但在時間和空間上每每是不節省的。
遞歸是一種比較好的程序設計方法,比較典型的範例是漢內塔、數學上的階乘以及最大公因子等問題。下面僅以階乘問題來講明遞歸。
階乘定義爲:if n!=1 n=0 if n!=n*(n-1)! n>1
程序設計方法:
第一遞歸結束條件,當階乘小於或等於1時,返回1。
第二遞歸執行部分,當階乘大於1時,返回n!=n*(n-1)!。
實現:
public static int recursion(int n) { if(n<=1) { return 1; }else { return n*recursion(n-1); } }
在遞歸程序中,主要就是一個堆棧的變化過程,程序執行過程當中,堆棧是由系統自動實現的,可是咱們應該可以將遞歸的程序變爲非遞歸的實現。
非遞歸程序中,須要瞭解的是什麼數據須要或什麼時間壓入堆棧,什麼數據須要或在何時出堆棧。
例如上例中的階乘問題,使用非遞歸實現,能夠考慮實現將不一樣的n壓入堆棧,每次減1,最後可以實現0的階乘的計算,而後返回,知道堆棧爲空爲止。
public static void main(String[] args) { //使用以前實現的鏈式堆棧 LinkedStack ls=new LinkedStack(); int num=10; while(num!=0) { ls.push(num); num--; } int product=1; while(!ls.isEmpty()) { product*=(int)ls.pop(); } System.out.println(product); }
上一篇:線性表(Linear List)
下一篇:隊列(queue)