帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之棧
1、棧的基本概念
棧(英語:stack)又稱為堆棧或堆疊,棧作為一種數(shù)據(jù)結(jié)構(gòu),是一種只能在一端進(jìn)行插入和刪除操作的特殊線性表。它按照先進(jìn)后出的原則存儲數(shù)據(jù),先進(jìn)入的數(shù)據(jù)被壓入棧底,最后的數(shù)據(jù)在棧頂,需要讀數(shù)據(jù)的時候從棧頂開始彈出數(shù)據(jù)(最后一個數(shù)據(jù)被第一個讀出來)。棧具有記憶作用,對棧的插入與刪除操作中,不需要改變棧底指針。
棧是允許在同一端進(jìn)行插入和刪除操作的特殊線性表。允許進(jìn)行插入和刪除操作的一端稱為棧頂(top),另一端為棧底(bottom);棧底固定,而棧頂浮動;棧中元素個數(shù)為零時稱為空棧。插入一般稱為進(jìn)棧(PUSH),刪除則稱為退棧(POP)。
由于堆疊數(shù)據(jù)結(jié)構(gòu)只允許在一端進(jìn)行操作,因而按照后進(jìn)先出(LIFO
, Last In First Out
)的原理運(yùn)作。棧也稱為后進(jìn)先出表。
這里以羽毛球筒為例,羽毛球筒就是一個棧,剛開始羽毛球筒是空的,也就是空棧,然后我們一個一個放入羽毛球,也就是一個一個push進(jìn)棧,當(dāng)我們需要使用羽毛球的時候,從筒里面拿,也就是pop出棧,但是第一個拿到的羽毛球是我們最后放進(jìn)去的。
2、Java模擬簡單的順序棧實(shí)現(xiàn)
package com.ys.datastructure; public class MyStack { private int[] array; private int maxSize; private int top; public MyStack(int size){ this.maxSize = size; array = new int[size]; top = -1; } //壓入數(shù)據(jù) public void push(int value){ if(top < maxSize-1){ array[++top] = value; } } //彈出棧頂數(shù)據(jù) public int pop(){ return array[top--]; } //訪問棧頂數(shù)據(jù) public int peek(){ return array[top]; } //判斷棧是否為空 public boolean isEmpty(){ return (top == -1); } //判斷棧是否滿了 public boolean isFull(){ return (top == maxSize-1); } }
測試:
package com.ys.test; import com.ys.datastructure.MyStack; public class MyStackTest { public static void main(String[] args) { MyStack stack = new MyStack(3); stack.push(1); stack.push(2); stack.push(3); System.out.println(stack.peek()); while(!stack.isEmpty()){ System.out.println(stack.pop()); } } }
結(jié)果:
這個棧是用數(shù)組實(shí)現(xiàn)的,內(nèi)部定義了一個數(shù)組,一個表示最大容量的值以及一個指向棧頂元素的top變量。構(gòu)造方法根據(jù)參數(shù)規(guī)定的容量創(chuàng)建一個新棧,push()方法是向棧中壓入元素,指向棧頂?shù)淖兞縯op加一,使它指向原頂端數(shù)據(jù)項(xiàng)上面的一個位置,并在這個位置上存儲一個數(shù)據(jù)。pop()方法返回top變量指向的元素,然后將top變量減一,便移除了數(shù)據(jù)項(xiàng)。要知道 top 變量指向的始終是棧頂?shù)脑亍?/p>
產(chǎn)生的問題:
- ①、上面棧的實(shí)現(xiàn)初始化容量之后,后面是不能進(jìn)行擴(kuò)容的(雖然棧不是用來存儲大量數(shù)據(jù)的),如果說后期數(shù)據(jù)量超過初始容量之后怎么辦?(自動擴(kuò)容)
- ②、我們是用數(shù)組實(shí)現(xiàn)棧,在定義數(shù)組類型的時候,也就規(guī)定了存儲在棧中的數(shù)據(jù)類型,那么同一個棧能不能存儲不同類型的數(shù)據(jù)呢?(聲明為Object)
- ③、棧需要初始化容量,而且數(shù)組實(shí)現(xiàn)的棧元素都是連續(xù)存儲的,那么能不能不初始化容量呢?(改為由鏈表實(shí)現(xiàn))
3、增強(qiáng)功能版棧
對于上面出現(xiàn)的問題,第一個能自動擴(kuò)容,第二個能存儲各種不同類型的數(shù)據(jù),解決辦法如下:(第三個在講鏈表的時候在介紹)
這個模擬的棧在JDK源碼中,大家可以參考 Stack 類的實(shí)現(xiàn)。
package com.ys.datastructure; import java.util.Arrays; import java.util.EmptyStackException; public class ArrayStack { //存儲元素的數(shù)組,聲明為Object類型能存儲任意類型的數(shù)據(jù) private Object[] elementData; //指向棧頂?shù)闹羔? private int top; //棧的總?cè)萘? private int size; //默認(rèn)構(gòu)造一個容量為10的棧 public ArrayStack(){ this.elementData = new Object[10]; this.top = -1; this.size = 10; } public ArrayStack(int initialCapacity){ if(initialCapacity < 0){ throw new IllegalArgumentException("棧初始容量不能小于0: "+initialCapacity); } this.elementData = new Object[initialCapacity]; this.top = -1; this.size = initialCapacity; } //壓入元素 public Object push(Object item){ //是否需要擴(kuò)容 isGrow(top+1); elementData[++top] = item; return item; } //彈出棧頂元素 public Object pop(){ Object obj = peek(); remove(top); return obj; } //獲取棧頂元素 public Object peek(){ if(top == -1){ throw new EmptyStackException(); } return elementData[top]; } //判斷棧是否為空 public boolean isEmpty(){ return (top == -1); } //刪除棧頂元素 public void remove(int top){ //棧頂元素置為null elementData[top] = null; this.top--; } /** * 是否需要擴(kuò)容,如果需要,則擴(kuò)大一倍并返回true,不需要則返回false * @param minCapacity * @return */ public boolean isGrow(int minCapacity){ int oldCapacity = size; //如果當(dāng)前元素壓入棧之后總?cè)萘看笥谇懊娑x的容量,則需要擴(kuò)容 if(minCapacity >= oldCapacity){ //定義擴(kuò)大之后棧的總?cè)萘? int newCapacity = 0; //棧容量擴(kuò)大兩倍(左移一位)看是否超過int類型所表示的最大范圍 if((oldCapacity<<1) - Integer.MAX_VALUE >0){ newCapacity = Integer.MAX_VALUE; }else{ newCapacity = (oldCapacity<<1);//左移一位,相當(dāng)于*2 } this.size = newCapacity; int[] newArray = new int[size]; elementData = Arrays.copyOf(elementData, size); return true; }else{ return false; } } }
測試:
//測試自定義棧類 ArrayStack //創(chuàng)建容量為3的棧,然后添加4個元素,3個int,1個String. @Test public void testArrayStack(){ ArrayStack stack = new ArrayStack(3); stack.push(1); //System.out.println(stack.peek()); stack.push(2); stack.push(3); stack.push("abc"); System.out.println(stack.peek()); stack.pop(); stack.pop(); stack.pop(); System.out.println(stack.peek()); }v
結(jié)果:
4、利用棧實(shí)現(xiàn)字符串逆序
我們知道棧是后進(jìn)先出,我們可以將一個字符串分隔為單個的字符,然后將字符一個一個push()進(jìn)棧,在一個一個pop()出棧就是逆序顯示了。如下:
將 字符串“how are you” 反轉(zhuǎn)?。?!
ps:這里我們是用上面自定的棧來實(shí)現(xiàn)的,大家可以將ArrayStack替換為JDK自帶的棧類Stack試試
//進(jìn)行字符串反轉(zhuǎn) @Test public void testStringReversal(){ ArrayStack stack = new ArrayStack(); String str = "how are you"; char[] cha = str.toCharArray(); for(char c : cha){ stack.push(c); } while(!stack.isEmpty()){ System.out.print(stack.pop()); } }
結(jié)果:
5、利用棧判斷分隔符是否匹配
寫過xml標(biāo)簽或者h(yuǎn)tml標(biāo)簽的,我們都知道<必須和最近的>進(jìn)行匹配,[ 也必須和最近的 ] 進(jìn)行匹配。
比如:<abc[123]abc>這是符號相匹配的,如果是 <abc[123>abc] 那就是不匹配的。
對于 12<a[b{c}]>,我們分析在棧中的數(shù)據(jù):遇到匹配正確的就消除
最后棧中的內(nèi)容為空則匹配成功,否則匹配失敗?。?!
//分隔符匹配 //遇到左邊分隔符了就push進(jìn)棧,遇到右邊分隔符了就pop出棧,看出棧的分隔符是否和這個有分隔符匹配 @Test public void testMatch(){ ArrayStack stack = new ArrayStack(3); String str = "12<a[b{c}]>"; char[] cha = str.toCharArray(); for(char c : cha){ switch (c) { case '{': case '[': case '<': stack.push(c); break; case '}': case ']': case '>': if(!stack.isEmpty()){ char ch = stack.pop().toString().toCharArray()[0]; if(c=='}' && ch != '{' || c==']' && ch != '[' || c==')' && ch != '('){ System.out.println("Error:"+ch+"-"+c); } } break; default: break; } } }
6、總結(jié)
根據(jù)棧后進(jìn)先出的特性,我們實(shí)現(xiàn)了單詞逆序以及分隔符匹配。所以其實(shí)棧是一個概念上的工具,具體能實(shí)現(xiàn)什么功能可以由我們?nèi)ハ胂?。棧通過提供限制性的訪問方法push()和pop(),使得程序不容易出錯。
對于棧的實(shí)現(xiàn),我們稍微分析就知道,數(shù)據(jù)入棧和出棧的時間復(fù)雜度都為O(1),也就是說棧操作所耗的時間不依賴棧中數(shù)據(jù)項(xiàng)的個數(shù),因此操作時間很短。而且需要注意的是棧不需要比較和移動操作,我們不要畫蛇添足?! ?/p>
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
java 中動態(tài)代理機(jī)制的實(shí)例講解
這篇文章主要介紹了java 中動態(tài)代理機(jī)制的實(shí)例講解的相關(guān)資料,希望通過本文大家能夠理解掌握動態(tài)代理機(jī)制,需要的朋友可以參考下2017-09-09Springboot Thymeleaf模板文件調(diào)用Java類靜態(tài)方法
這篇文章主要介紹了Springboot Thymeleaf模板文件調(diào)用Java類靜態(tài)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2007-09-09Springboot 使用內(nèi)置tomcat禁止不安全HTTP的方法
這篇文章主要介紹了Springboot 使用內(nèi)置tomcat禁止不安全HTTP的方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Java網(wǎng)絡(luò)編程之IO模型阻塞與非阻塞簡要分析
這篇文章主要介紹Java網(wǎng)絡(luò)編程中的IO模型阻塞與非阻塞簡要分析,文中附有示例代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-09-09maven多模塊項(xiàng)目依賴管理與依賴?yán)^承詳解
這篇文章主要介紹了maven多模塊項(xiàng)目依賴管理與依賴?yán)^承詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12