數據結構與算法學習筆記之后進先出的“桶”


前言

棧最為一種的常用的數據結構,用“桶”來形容最合適不過;今天我們就來學習一下

正文

一、棧的定義?


1.“后進先出,先進后出”的數據結構。
2.從操作特性來看,是一種“操作受限”的線性表,只可以在一端插入和刪除數據。

 

二、為什么需要棧?

 

1.任何數據結構都是對特定應用場景的抽象,棧是一種操作受限的數據結構,其操作特性用數組和鏈表均可實現,但卻暴露太多的操作接口,使用時容易出錯;


2.當某個數據集合只涉及在一端插入和刪除數據,且滿足后進者先出,先進者后出的操作特性時,我們應該首選棧這種數據結構

 

三、如何實現棧?

 棧可以用數組,鏈表來實現

1.以數組為例

空間復雜度為O(1)

時間復雜度大多數為O(1),特殊情況自動擴容拷貝原數組時為O(n);

均攤時間復雜度接近於O(1);

// 基於數組實現的順序棧
public class ArrayStack { private String[] items;  // 數組
  private int count;       // 棧中元素個數
  private int n;           // 棧的大小 // 初始化數組,申請一個大小為 n 的數組空間
  public ArrayStack(int n) { this.items = new String[n]; this.n = n; this.count = 0; } // 入棧操作
  public boolean push(String item) { // 數組空間不夠了,直接返回 false,入棧失敗。
    if (count == n) return false; // 將 item 放到下標為 count 的位置,並且 count 加一
    items[count] = item; ++count; return true; } // 出棧操作
  public String pop() { // 棧為空,則直接返回 null
    if (count == 0) return null; // 返回下標為 count-1 的數組元素,並且棧中元素個數 count 減一
    String tmp = items[count-1]; --count; return tmp; } }

 (代碼來自於網絡,如有侵權請告知我刪除)

2.以鏈表為例(網上找的)

空間復雜度為O(1)

時間復雜度大多數為O(1)

public class StackOfLinked<Item> implements Iterable<Item> { //定義一個內部類,就可以直接使用類型參數
private class Node{ Item item; Node next; } private Node first; private int N; //構造器
public StackOfLinked(){} //添加
public void push(Item item){ Node oldfirst = first; first = new Node(); first.item = item; first.next = oldfirst; N++; } //刪除
public Item pop(){ Item item = first.item; first = first.next; N--; return item; } //是否為空
public boolean isEmpty(){ return N == 0; } //元素數量
public int size(){ return N; } //返回棧中最近添加的元素而不刪除它
public Item peek(){ return first.item; } @Override public Iterator<Item> iterator() { return new LinkedIterator(); } //內部類:迭代器
class LinkedIterator implements Iterator{ int i = N; Node t = first; @Override public boolean hasNext() { return i > 0; } @Override public Item next() { Item item = (Item) t.item; t = t.next; i--; return item; } } }

(代碼來自於網絡,如有侵權請告知我刪除)

四、棧的應用

1.函數調用中的應用

操作系統給每個線程分配了一塊獨立的內存空間,這塊內存被組織成“棧”這種結構,用來存儲函數調用時的臨時變量。每進入一個函數,就會將其中的臨時變量作為棧幀入棧,當被調用函數執行完成,返回之后,將這個函數對應的棧幀出棧。

 

2.在表達式求值中的應用(比如:34+13*9+44-12/3)

利用兩個棧,一個用來保存操作數,一個用來保存運算符。我們從左向右遍歷表達式,當遇到數字,我們就直接壓入操作數棧;當遇到運算符,就與運算符棧的棧頂元素進行比較,若比運算符棧頂元素優先級高,就將當前運算符壓入棧,若比運算符棧頂元素的優先級低或者相同,從運算符棧中取出棧頂運算符,從操作數棧頂取出2個操作數,然后進行計算,把計算完的結果壓入操作數棧,繼續比較。

  (圖片來自於王爭)

3.棧在括號匹配中的應用(比如:{}{[()]()})

用棧保存為匹配的左括號,從左到右一次掃描字符串,當掃描到左括號時,則將其壓入棧中;當掃描到右括號時,從棧頂取出一個左括號,如果能匹配上,則繼續掃描剩下的字符串。如果掃描過程中,遇到不能配對的右括號,或者棧中沒有數據,則說明為非法格式。
當所有的括號都掃描完成之后,如果棧為空,則說明字符串為合法格式;否則,說明未匹配的左括號為非法格式。

 

4.如何實現瀏覽器的前進后退功能?


我們使用兩個棧X和Y,我們把首次瀏覽的頁面依次壓如棧X,當點擊后退按鈕時,再依次從棧X中出棧,並將出棧的數據一次放入Y棧。當點擊前進按鈕時,我們依次從棧Y中取出數據,放入棧X中。當棧X中沒有數據時,說明沒有頁面可以繼續后退瀏覽了。當Y棧沒有數據,那就說明沒有頁面可以點擊前進瀏覽了。

(圖片來自於王爭)

 

五、兩個問題

1. 我們在講棧的應用時,講到用函數調用棧來保存臨時變量,為什么函數調用要用“棧”來保存臨時變量呢?用其他數據結構不行嗎?


答:因為函數調用的執行順序符合后進者先出,先進者后出的特點。

函數調用中經常嵌套,栗子:A調用B,B又調用C,那么就需要先把C執行完,結果賦值給B中的臨時變量,B的執行結果再賦值給A的臨時變量,嵌套越深的函數越需要被先執行,這樣剛好符合棧的特點,因此每次遇到函數調用,只需要壓棧,最后依次從棧頂彈出依次執行即可,根據數據結構是特定應用場景的抽象的原則,我們優先考慮棧結構。

 

2.我們都知道,JVM 內存管理中有個“堆棧”的概念。棧內存用來存儲局部變量和方法調用,堆內存用來存儲 Java 中的對象。那 JVM 里面的“棧”跟我們這里說的“棧”是不是一回事呢?如果不是,那它為什么又叫作“棧”呢?

 

答:內存中的堆棧和數據結構堆棧不是一個概念,可以說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。
內存空間在邏輯上分為三部分:代碼區、靜態數據區和動態數據區,動態數據區又分為棧區和堆區。
代碼區:存儲方法體的二進制代碼。高級調度(作業調度)、中級調度(內存調度)、低級調度(進程調度)控制代碼區執行代碼的切換。
靜態數據區:存儲全局變量、靜態變量、常量,常量包括final修飾的常量和String常量。系統自動分配和回收。
棧區:存儲運行方法的形參、局部變量、返回值。由系統自動分配和回收。
堆區:new一個對象的引用或地址存儲在棧區,指向該對象存儲在堆區中的真實數據。

 

相關文章

數據結構與算法學習筆記之寫鏈表代碼的正確姿勢(下)

數據結構與算法學習筆記之 提高讀取性能的鏈表(上)

數據結構與算法學習筆記之 從0編號的數組

 

 

以上內容為個人的學習筆記,僅作為學習交流之用。

歡迎大家關注公眾號,不定時干貨,只做有價值的輸出

作者:Dawnzhang 
出處:https://www.cnblogs.com/clwydjgs/p/9792256.html

版權:本文版權歸作者
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則必究法律責任


免責聲明!

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



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