棧
定義
棧是一種“操作受限”的線性表,只允許在一端插入(入棧push)和刪除(出棧pop)數(shù)據(jù)评腺。
-
棧既可以用數(shù)組來實現(xiàn)帘瞭,也可以用鏈表來實現(xiàn)。用數(shù)組實現(xiàn)的棧蒿讥,我們叫作順序棧蝶念,用鏈表實現(xiàn)的棧,我們叫作鏈?zhǔn)綏?/strong>芋绸。
基于數(shù)組實現(xiàn):
// 基于數(shù)組實現(xiàn)的順序棧 public class ArrayStack { private String[] items; // 數(shù)組 private int count; // 棧中元素個數(shù) private int n; //棧的大小 // 初始化數(shù)組媒殉,申請一個大小為n的數(shù)組空間 public ArrayStack(int n) { this.items = new String[n]; this.n = n; this.count = 0; } // 入棧操作 public boolean push(String item) { // 數(shù)組空間不夠了,直接返回false摔敛,入棧失敗廷蓉。 if (count == n) return false; // 將item放到下標(biāo)為count的位置,并且count加一 items[count] = item; ++count; return true; } // 出棧操作 public String pop() { // 棧為空马昙,則直接返回null if (count == 0) return null; // 返回下標(biāo)為count-1的數(shù)組元素桃犬,并且棧中元素個數(shù)count減一 String tmp = items[count-1]; --count; return tmp; } }
基于鏈表實現(xiàn):
//TODO 待添加
特性
- 后進(jìn)者先出,先進(jìn)者后出行楞,這就是典型的“椩芟荆”結(jié)構(gòu)。
入棧子房、出棧的時間形用、空間復(fù)雜度
在入棧和出棧過程中就轧,只需要一兩個臨時變量存儲空間,所以空間復(fù)雜度是 O(1)尾序。
不管是順序棧還是鏈?zhǔn)綏5龇幔霔G椤⒊鰲V簧婕皸m攤€別數(shù)據(jù)的操作每币,所以時間復(fù)雜度都是 O(1)。
適用場景
當(dāng)某個數(shù)據(jù)集合只涉及在一端插入和刪除數(shù)據(jù)琢歇,并且滿足后進(jìn)先出兰怠、先進(jìn)后出的特性,這時我們就應(yīng)該首選“椑蠲#”這種數(shù)據(jù)結(jié)構(gòu)揭保。
-
棧在函數(shù)調(diào)用中的應(yīng)用 --函數(shù)調(diào)用棧
操作系統(tǒng)給每個線程分配了一塊獨立的內(nèi)存空間,這塊內(nèi)存被組織成“椘呛辏”這種結(jié)構(gòu), 用來存儲函數(shù)調(diào)用時的臨時變量秸侣。每進(jìn)入一個函數(shù),就會將臨時變量作為一個棧幀入棧宠互,當(dāng)被調(diào)用函數(shù)執(zhí)行完成味榛,返回之后,將這個函數(shù)對應(yīng)的棧幀出棧
-
棧在表達(dá)式求值中的應(yīng)用 -- 表達(dá)式求值
計算器如何計算四則運算予跌,如計算器如何計算34+13*9+44-12/3 = 搏色?
實際上,編譯器就是通過兩個棧來實現(xiàn)的券册。其中一個保存操作數(shù)的棧频轿,另一個是保存運算符的棧。我們從左向右遍歷表達(dá)式烁焙,當(dāng)遇到數(shù)字航邢,我們就直接壓入操作數(shù)棧;當(dāng)遇到運算符骄蝇,就與運算符棧的棧頂元素進(jìn)行比較膳殷。
如果比運算符棧頂元素的優(yōu)先級高,就將當(dāng)前運算符壓入棧乞榨;如果比運算符棧頂元素的優(yōu)先級低或者相同秽之,從運算符棧中取棧頂運算符,從操作數(shù)棧的棧頂取 2 個操作數(shù)吃既,然后進(jìn)行計算考榨,再把計算完的結(jié)果壓入操作數(shù)棧,繼續(xù)比較鹦倚。
棧--表達(dá)式求值 -
棧在括號匹配中的應(yīng)用 -- 編譯器中格式的實時校驗
假設(shè)表達(dá)式中只包含三種括號河质,圓括號 ()、方括號[]和花括號{},并且它們可以任意嵌套掀鹅。比如散休,{[] ()[{}]}或[{()}([])]等都為合法格式,而{[}()]或[({)]為不合法的格式乐尊。那我現(xiàn)在給你一個包含三種括號的表達(dá)式字符串戚丸,如何檢查它是否合法呢?這里也可以用棧來解決扔嵌。我們用棧來保存未匹配的左括號限府,從左到右依次掃描字符串。當(dāng)掃描到左括號時痢缎,則將其壓入棧中胁勺;當(dāng)掃描到右括號時,從棧頂取出一個左括號独旷。如果能夠匹配署穗,比如“(”跟“)”匹配,“[”跟“]”匹配嵌洼,“{”跟“}”匹配案疲,則繼續(xù)掃描剩下的字符串。如果掃描的過程中咱台,遇到不能配對的右括號络拌,或者棧中沒有數(shù)據(jù),則說明為非法格式回溺。當(dāng)所有的括號都掃描完成之后春贸,如果棧為空,則說明字符串為合法格式遗遵;否則萍恕,說明有未匹配的左括號,為非法格式车要。
小結(jié)
棧是一種操作受限的數(shù)據(jù)結(jié)構(gòu)允粤,只支持入棧和出棧操作。后進(jìn)先出是它最大的特點翼岁。棧既可以通過數(shù)組實現(xiàn)类垫,也可以通過鏈表來實現(xiàn)。不管基于數(shù)組還是鏈表琅坡,入棧悉患、出棧的時間復(fù)雜度都為 O(1)
案例:
括號驗證
最小棧
區(qū)間最大值
題目:找到區(qū)間的最大值,
案例1 輸入A = [6,2,1] 輸出 36案例2 輸入 A = [5,2,3,4,1]; 輸出 28
求解過程:
[6] 6 * 6 = 36
[2] 2*2 = 4
[6,2] 2* (6+2) = 16
[6,2,1] 1 * (6+2+1) = 9
[2,1] 1*(2+1) = 3
[1] 1*1 = 1
區(qū)間值 = 區(qū)間最小數(shù) * 區(qū)間和
區(qū)間和 可通過前綴和來存儲榆俺,前綴和sum[i] ,其中i對應(yīng)A的下標(biāo)+1售躁,sum[i] = A[0...i]的累計和
如A = [6,2,1]Sum = [0,6,8,9]
class Solution{ //思路:區(qū)間值 = 最小數(shù) * 區(qū)間和坞淮,區(qū)間和 可通過前綴和來存儲, // 區(qū)間的最小數(shù)使用 棧 來存儲數(shù)組下標(biāo) public static int maximumRange(int[] numberArray) { //邊界條件判斷 if(numberArray == null || numberArray.length == 0){ return 0; } int maxNumber = 0; // 存儲最小值 Stack<Integer> stack = new Stack<>(); // 設(shè)置前綴和 int[] sumArray = new int[numberArray.length+1]; // 求前綴和數(shù)組 for (int i = 0; i < numberArray.length; i++) { sumArray[i+1] = sumArray[i]+numberArray[i]; } for (int i = 0; i < numberArray.length; i++) { // z =x * y; x一定的情況下陪捷,y越大回窘,則z越大;假設(shè) 最小數(shù) = current 計算以current為最小值的最大區(qū)間市袖; // 當(dāng)前值 < 假定的最小數(shù)current啡直, 此時獲取以current 為最小值的最大區(qū)間,并加以求積。 while(!stack.isEmpty() && numberArray[i] < numberArray[stack.peek()]){ int current= stack.pop(); // 區(qū)間范圍 int left = i; int right = i; if(stack.isEmpty()){ left = 0; }else{ left = current; } // 以current為最小值的最大區(qū)間和 int interval = sumArray[right] - sumArray[left]; // 求積,注意:stack存儲下標(biāo)的目的:用 O(1)取到前綴和 maxNumber = Math.max(maxNumber, (interval*numberArray[current])); } stack.push(i); } while(!stack.isEmpty()){ int current= stack.pop(); //區(qū)間范圍 int left = numberArray.length; int right = numberArray.length; if(stack.isEmpty()){ left = 0; }else{ left = current; } int interval = sumArray[right] - sumArray[left]; //計算自己求集 maxNumber = Math.max(maxNumber, (interval*numberArray[current])); } return maxNumber; } }
2.最小棧
3.接雨水
隊列
定義
隊列跟棧一樣窥淆,也是一種操作受限的線性表數(shù)據(jù)結(jié)構(gòu)钞馁,最基本的操作也是兩個:入隊 enqueue(),放一個數(shù)據(jù)到隊列尾部二打;出隊 dequeue()县忌,從隊列頭部取一個元素。
-
用數(shù)組實現(xiàn)的隊列叫作順序隊列继效,用鏈表實現(xiàn)的隊列叫作鏈?zhǔn)疥犃?/strong>症杏。
順序隊列:
基于數(shù)組實現(xiàn)需要兩個指針:一個是 head 指針,指向隊頭瑞信;一個是 tail 指針厉颤,指向隊尾。
順序隊列假設(shè)當(dāng) a凡简、b逼友、c、d 依次入隊之后秤涩,隊列中的 head 指針指向下標(biāo)為 0 的位置帜乞,tail 指針指向下標(biāo)為 4 的位置。
順序隊列-init當(dāng)我們調(diào)用兩次出隊操作之后筐眷,隊列中 head 指針指向下標(biāo)為 2 的位置黎烈,tail 指針仍然指向下標(biāo)為 4 的位置。
順序隊列-move隨著不停地進(jìn)行入隊匀谣、出隊操作照棋,head 和 tail 都會持續(xù)往后移動。當(dāng) tail 移動到最右邊武翎,即使數(shù)組中還有空閑空間烈炭,也無法繼續(xù)往隊列中添加數(shù)據(jù)了,這時候后频,如果有新的數(shù)據(jù)入隊梳庆,我們可以將 head 到 tail 之間的數(shù)據(jù)暖途,整體搬移到數(shù)組中 0 到 tail-head 的位置。
// 用數(shù)組實現(xiàn)的隊列 public class ArrayQueue { // 數(shù)組:items膏执,數(shù)組大凶な邸:n private String[] items; private int n = 0; // head表示隊頭下標(biāo),tail表示隊尾下標(biāo) private int head = 0; private int tail = 0; // 申請一個大小為capacity的數(shù)組 public ArrayQueue(int capacity) { items = new String[capacity]; n = capacity; } // 入隊操作更米,將item放入隊尾 public boolean enqueue(String item) { // tail == n表示隊列末尾沒有空間了 if (tail == n) { // tail ==n && head==0欺栗,表示整個隊列都占滿了 if (head == 0) return false; // 數(shù)據(jù)搬移 for (int i = head; i < tail; ++i) { items[i-head] = items[i]; } // 搬移完之后重新更新head和tail tail -= head; head = 0; } items[tail] = item; ++tail; return true; } // 出隊 public String dequeue() { // 如果head == tail 表示隊列為空 if (head == tail) return null; // 為了讓其他語言的同學(xué)看的更加明確,把--操作放到單獨一行來寫了 String ret = items[head]; ++head; return ret; } }
鏈?zhǔn)疥犃校?/strong>
基于鏈表的實現(xiàn)征峦,我們同樣需要兩個指針:head 指針和 tail 指針迟几。它們分別指向鏈表的第一個結(jié)點和最后一個結(jié)點。如圖所示栏笆,入隊時类腮,tail->next= new_node, tail = tail->next;出隊時蛉加,head = head->next
鏈?zhǔn)疥犃?/div>//TODO 待添加
特性
先進(jìn)者先出蚜枢,這就是典型的“隊列”。
分類
循環(huán)隊列
- 定義:循環(huán)隊列针饥,顧名思義厂抽,它長得像一個環(huán)。原本數(shù)組是有頭有尾的丁眼,是一條直線】攴铮現(xiàn)在我們把首尾相連,扳成了一個環(huán)苞七。優(yōu)點:可以解決順序隊列中tail == null 出現(xiàn)數(shù)據(jù)搬移的問題藐守。
循環(huán)隊列解釋:圖中這個隊列的大小為 8,當(dāng)前 head=4莽鸭,tail=7吗伤。當(dāng)有一個新的元素 a 入隊時,我們放入下標(biāo)為 7 的位置硫眨。但這個時候足淆,我們并不把 tail 更新為 8,而是將其在環(huán)中后移一位礁阁,到下標(biāo)為 0 的位置巧号。當(dāng)再有一個元素 b 入隊時,我們將 b 放入下標(biāo)為 0 的位置姥闭,然后 tail 加 1 更新為 1丹鸿,通過這樣的方法,我們成功避免了數(shù)據(jù)搬移操作棚品。
注意事項:
- 循環(huán)隊列實現(xiàn)需要:確定好隊空和隊滿的判定條件
- 隊空的判斷條件是: head == tail
- 隊滿的判斷條件是:(tail+1)%n=head
-
當(dāng)隊列滿時靠欢,圖中的 tail 指向的位置實際上是沒有存儲數(shù)據(jù)的廊敌。所以,循環(huán)隊列會浪費一個數(shù)組的存儲空間门怪。
public class CircularQueue { // 數(shù)組:items骡澈,數(shù)組大小:n private String[] items; private int n = 0; // head表示隊頭下標(biāo)掷空,tail表示隊尾下標(biāo) private int head = 0; private int tail = 0; // 申請一個大小為capacity的數(shù)組 public CircularQueue(int capacity) { items = new String[capacity]; n = capacity; } // 入隊 public boolean enqueue(String item) { // 隊列滿了 if ((tail + 1) % n == head) return false; items[tail] = item; tail = (tail + 1) % n; return true; } // 出隊 public String dequeue() { // 如果head == tail 表示隊列為空 if (head == tail) return null; String ret = items[head]; head = (head + 1) % n; return ret; } }
阻塞隊列
定義:在隊列基礎(chǔ)上增加了阻塞操作肋殴。簡單來說,就是在隊列為空的時候坦弟,從隊頭取數(shù)據(jù)會被阻塞护锤。因為此時還沒有數(shù)據(jù)可取,直到隊列中有了數(shù)據(jù)才能返回酿傍;如果隊列已經(jīng)滿了烙懦,那么插入數(shù)據(jù)的操作就會被阻塞,直到隊列中有空閑位置后再插入數(shù)據(jù)拧粪,然后再返回修陡。
案例:基于阻塞隊列,輕松實現(xiàn)一個“生產(chǎn)者 - 消費者模型”可霎!
阻塞隊列并發(fā)隊列
定義:線程安全的隊列我們叫作并發(fā)隊列。最簡單直接的實現(xiàn)方式是直接在 enqueue()宴杀、dequeue() 方法上加鎖癣朗,但是鎖粒度大并發(fā)度會比較低,同一時刻僅允許一個存或者取操作旺罢。實際上旷余,基于數(shù)組的循環(huán)隊列,利用 CAS 原子操作扁达,可以實現(xiàn)非常高效的并發(fā)隊列正卧。這也是循環(huán)隊列比鏈?zhǔn)疥犃袘?yīng)用更加廣泛的原因。
案例:Disruptor
小結(jié)
隊列最大的特點就是**先進(jìn)先出**跪解,主要的兩個操作是**入隊**和**出隊**炉旷。跟棧一樣,它既可以用數(shù)組來實現(xiàn)叉讥,也可以用鏈表來實現(xiàn)窘行。用數(shù)組實現(xiàn)的叫**順序隊列**,用鏈表實現(xiàn)的叫**鏈?zhǔn)疥犃?*图仓。特別是長得像一個環(huán)的**循環(huán)隊列**罐盔。在數(shù)組實現(xiàn)隊列的時候,會有數(shù)據(jù)搬移操作救崔,要想**解決數(shù)據(jù)搬移**的問題惶看,我們就需要像環(huán)一樣的循環(huán)隊列捏顺。 **循環(huán)隊列**是我們這節(jié)的重點。要想寫出沒有 bug 的循環(huán)隊列實現(xiàn)代碼纬黎,關(guān)鍵要**確定好隊空和隊滿的判定條件**幅骄,具體的代碼你要能寫出來。 **阻塞隊列**就是入隊莹桅、出隊操作可以阻塞昌执,**并發(fā)隊列**就是隊列的操作多線程安全。
案例:
用棧實現(xiàn)隊列
class MyQueue { //題意要求使用兩個棧(先進(jìn)后出)來實現(xiàn)隊列(先進(jìn)先出)诈泼,思路:設(shè)置一個入隊棧懂拾,一個出隊棧,如果有數(shù)據(jù)過來铐达,保存到入堆棧中岖赋,如果有需要去除數(shù)據(jù),則從出隊棧取出瓮孙,出隊棧如果沒有數(shù)據(jù)唐断,將入堆棧中的數(shù)據(jù)一次壓入出隊棧中棧(PS:此操作通過將入隊棧的數(shù)據(jù)搬移到出隊棧實現(xiàn)了將先先進(jìn)先出的特性) private Stack<Integer> enqueue = new Stack<>(); private Stack<Integer> dequeue = new Stack<>(); /** Initialize your data structure here. */ public MyQueue() { } /** Push element x to the back of queue. */ public void push(int x) { enqueue.push(x); } /** Removes the element from in front of queue and returns that element. */ public int pop() { if(!dequeue.isEmpty()){ return dequeue.pop(); } if(enqueue.isEmpty()){ return 0; }else{ while(!enqueue.isEmpty()){ dequeue.push(enqueue.pop()); } return dequeue.pop(); } } /** Get the front element. */ public int peek() { if(!dequeue.isEmpty()){ return dequeue.peek(); } if(enqueue.isEmpty()){ return 0; }else{ while(!enqueue.isEmpty()){ dequeue.push(enqueue.pop()); } return dequeue.peek(); } } /** Returns whether the queue is empty. */ public boolean empty() { return enqueue.isEmpty() && dequeue.isEmpty(); } } /** * Your MyQueue object will be instantiated and called as such: * MyQueue obj = new MyQueue(); * obj.push(x); * int param_2 = obj.pop(); * int param_3 = obj.peek(); * boolean param_4 = obj.empty(); */
推薦結(jié)果打散
題目:存在一個推薦視頻v和圖片p的系統(tǒng),現(xiàn)在需要對推薦系統(tǒng)結(jié)果進(jìn)行打散杭抠,要求圖片p的結(jié)果每n個里面出現(xiàn)一次脸甘,并且保證圖片最早出現(xiàn)的位置不變,圖片之前的相對位置不變偏灿。
案例1:v_0,v_1,v_2,p_3,p_4,p_5,v_6,p_7,v_8,v_9;要求:每3個里面存著一個圖片丹诀,最后不滿足的圖片刪掉;
v_0,v_1,v_2,p_3,v_6,v_8,p_4,v_9class Solution{ /** * 推薦結(jié)果打散 * //分析:根據(jù)題意要求:視頻和圖片原來相對先后順序保持不變,所以選擇隊列這種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)翁垂; * 1铆遭、如果原集合中開始是視頻則,直接添加到返回結(jié)果沿猜; * 2枚荣、如果遇到圖片則按照規(guī)則--即每maxInterval中添加一個圖片; * * @param videoAndPicList 圖片和視頻混合集合 * @param maxInterval 最大間隔 * @return */ public static List<String> getRecommendenResult(List<String> videoAndPicList, int maxInterval) { List<String> result = new ArrayList<>(); //邊界條件 if(videoAndPicList == null || videoAndPicList.isEmpty()){ return result; } //定義兩個隊列 Deque<String> videoDeque = new LinkedList<>(); Deque<String> picDeque = new LinkedList<>(); int index = 0; int size = videoAndPicList.size(); //如果原集合中開始是視頻則啼肩,直接添加到返回結(jié)果 while(index < size && isVidieo(videoAndPicList.get(index))){ result.add(videoAndPicList.get(index)); index++; } //將混合結(jié)果拆分到相應(yīng)的隊列中 for (int i = index; i < size; i++) { String clip = videoAndPicList.get(i); if(isVidieo(clip)){ videoDeque.add(clip); }else { picDeque.add(clip); } } int current = result.size(); while(!videoDeque.isEmpty() && !picDeque.isEmpty()){ if(current < maxInterval){ result.add(videoDeque.poll()); current++; }else { result.add(picDeque.poll()); current = 0; } } while(!videoDeque.isEmpty()){ result.add(videoDeque.poll()); } if(current >= maxInterval && !picDeque.isEmpty()){ result.add(picDeque.poll()); } return result; } /** * 判斷是否為視頻 * * @param clip * @return */ private static boolean isVidieo(String clip) { if(clip.indexOf("v") != -1){ return true; } return false; } }
最后編輯于 :?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者禁止轉(zhuǎn)載橄妆,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。- 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹦漠,“玉大人椭员,你說我怎么就攤上這事〉言埃” “怎么了隘击?”我有些...
- 文/不壞的土叔 我叫張陵,是天一觀的道長研铆。 經(jīng)常有香客問我埋同,道長,這世上最難降的妖魔是什么棵红? 我笑而不...
- 正文 為了忘掉前任凶赁,我火速辦了婚禮,結(jié)果婚禮上逆甜,老公的妹妹穿的比我還像新娘虱肄。我一直安慰自己,他們只是感情好交煞,可當(dāng)我...
- 文/花漫 我一把揭開白布咏窿。 她就那樣靜靜地躺著,像睡著了一般素征。 火紅的嫁衣襯著肌膚如雪翰灾。 梳的紋絲不亂的頭發(fā)上,一...
- 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼揭璃!你這毒婦竟也來了晚凿?” 一聲冷哼從身側(cè)響起,我...
- 正文 年R本政府宣布拨扶,位于F島的核電站凳鬓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屈雄。R本人自食惡果不足惜村视,卻給世界環(huán)境...
- 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酒奶。 院中可真熱鬧蚁孔,春花似錦、人聲如沸惋嚎。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽另伍。三九已至鼻百,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摆尝,已是汗流浹背温艇。 一陣腳步聲響...
推薦閱讀更多精彩內(nèi)容
- 數(shù)組 1:什么是數(shù)組?2:Java中數(shù)組的聲明及數(shù)組的遍歷3:數(shù)組天生的優(yōu)勢——索引4:動態(tài)數(shù)組5:封裝自己的數(shù)組...
- 棧 定義 棧是一種操作受限的線性表砾隅,只支持在棧頂入棧(push)和出棧(pop)操作,有后進(jìn)先出的特性债蜜∏绻。可用數(shù)組或...
- 從廣義上來講:數(shù)據(jù)結(jié)構(gòu)就是一組數(shù)據(jù)的存儲結(jié)構(gòu) 恼蓬, 算法就是操作數(shù)據(jù)的方法數(shù)據(jù)結(jié)構(gòu)是為算法服務(wù)的,算法是要作用在特定...
- 最近得空僵芹,想寫篇文章好好說說 java 線程池問題处硬,我相信很多人都一知半解的,包括我自己在仔仔細(xì)細(xì)看源碼之前拇派,也有...