數據結構與算法筆記:基礎篇 - 棧:如何實現瀏覽器的前進和后退功能?

概述

瀏覽器的前進、后退功能,你肯定很熟悉吧?

當依次訪問完一串頁面 a-b-c 之后,點擊瀏覽器的后退按鈕,就可以查看之前瀏覽過的頁面 ba。當后退到頁面 a,點擊前進按鈕,就可以重新查看頁面 bc。但是,如果你后退到頁面 b 后,點擊新的頁面 d,那就無法再通過前進、后退功能查看頁面 c 了。

假設你是瀏覽器的開發工程師,你會如何實現這個功能呢?

這就要用到本章講的 “棧” 這種數據結構了。


如何理解 “棧”?

關于 “棧”,有一個非常貼切的例子,就是一摞疊在一起的盤子。我們平時放盤子時,都是從下往上一個一個的放;取的時候,也是從上往下一個一個地依次取,不能從中間任意抽出。后進者先出,先進者后出,這就是典型的 “棧” 結構

從棧的操作特性上來看,棧是一種 “操作受限” 的線性表,只允許在一端插入和刪除數據。

第一次接觸這種數據類型時,我對它存在的意義產生了很大的疑惑。因為我覺得,相比數組和鏈表,棧給我的只有限制,并沒有任何優勢。那我直接使用數組或鏈表不就好了嗎?為什么還要用這個 “操作受限” 的 “棧” 呢?

事實上,從功能上來說,數組或鏈表確實可以替代棧,但你要知道,特定的數據結構是對特定場景的抽象,而且,數組或鏈表暴露了太多的接口,操作上的確靈活,但使用時就比較不可控,自然也就容易出錯。

當某個數據集合只涉及在一端插入和刪除數據,并且滿足后進先出、先進后出的特性,這是應該首選 “棧” 這種數據結構

如何實現一個 “棧”?

從剛才棧的定義里,我們可以看出,棧主要包含兩個操作,入棧和出棧,也就是在棧頂插入一個數據和從棧頂刪除一個數據。理解了棧的定義后,我們來看下如何用代碼實現一個棧。

實際上,棧既可以用數組來實現,也可以用鏈表來實現。

  • 用數組實現的棧,我們叫做順序棧
  • 用鏈表實現的棧,我們叫做鏈式棧

這里實現一個基于數組的順序棧。

這段代碼使用 Java 來實現,但不涉及任何高級語法,并且用了中文做了詳細的注釋。

public class ArrayStack {private String[] items; // 數組private int count; // 棧中元素個數private int n; // 棧大小public ArrayStack(int n) {this.items = new String[n];this.count = 0;this.n = n;}// 入棧操作public boolean push(String item) {if (count == n) {// 數組空間不夠了,直接返回false,入棧失敗return false;}items[count] = item;count++;return true;}// 出棧操作public String pop() {if (count == 0) {// 棧為空,直接返回nullreturn null;}// 返回下標為count-1的數組元素,并且棧中元素個數減1String temp = items[count - 1];count--;return temp;}
}

了解了定義和基本操作,那它的操作時間、框架復雜度時多少呢?

不管是順序棧還是鏈式棧,存儲數據只需要一個大小為 n 的數組就夠了。在入棧和出棧的過程中,只需要一兩個臨時變量存儲空間,所以空間復雜度時 O ( 1 ) O(1) O(1)

注意,這里存儲數據需要一個大小為 n 的數組,并不是說空間復雜度就是 O ( n ) O(n) O(n)。因為,這 n 個空間是必須的,無法省掉。所以我們說空間復雜度的時候,是除了原本的數據存儲空間外,算法運行還需要額外的存儲空間。

框架復雜度分析是不是很簡單?時間復雜度也不難。不管是順序棧還是鏈式棧,入棧、出棧只涉及棧頂個人數據的操作,所以時間復雜度都是 O ( 1 ) O(1) O(1)

支持動態擴容的順序棧

剛才那個基于數組實現的棧,是一個固定大小的棧,也就是說,在初始化棧時需要實現制定棧的大小。當棧滿之后,就無法再往棧里添加數據了。盡管鏈式棧的大小不受限,但要存儲 next 指針,內存消耗相對較多。那我們如何基于數組實現一個可以支持動態擴容的棧呢?

還記得在數組那篇文章,是如何來支持一個支持動態擴容的數組嗎?當數組空間不夠時,我們就重新申請一塊更大的內存,將原來數組中的數據統統拷貝過去。這樣就實現了一個支持動態擴容的數組。

所以,如果要實現一個支持動態擴容的棧,我們只需要底層依賴一個支持動態擴容的數組就可以了。當棧滿了之后,我們就申請一個更大的數組,將原來的數據搬移到新數組中。

在這里插入圖片描述
實際上,支持動態擴容的順序棧,我們平時開發中并不常用到。講這個的目的,主要還是希望帶你練習一下前面將的復雜度分析方法。

你不用死記硬背入棧、出棧的時間復雜度,你需要掌握的是分析方法。能夠自己分析才算是真正掌握了。現在就帶你一起分析一下支持動態擴容的順序棧的入棧、出棧的時間復雜度。

對于出棧操作來說,我們不會涉及內存的重新申請和數據搬移,所以出棧的時間復雜度還是 O ( 1 ) O(1) O(1)。但是對于入棧來說,當占用有空閑空間時,入棧操作的時間復雜度是 O ( 1 ) O(1) O(1)。但當空間不夠時,就需要申請內存和數據搬移,所以時間復雜度編程了 O ( n ) O(n) O(n)

也就是說,對于入棧操作,最好情況時間復雜度是 O ( 1 ) O(1) O(1),最壞情況時間復雜度是 O ( n ) O(n) O(n)。那平均情況下的時間復雜度又是多少呢?還記得我們在復雜度那篇文章中講的攤還分析法嗎?這個入棧操作的平均時間復雜度可以用攤還分析法來分析。正好也借此再回顧一下攤還分析法。

為了分析的方便,我們需要預先做一些假設和定義:

  • 棧空間不夠時,我們重新申請一個原來大小兩倍的數組;
  • 為了簡化分析,假設只有入棧操作沒有出棧操作;
  • 定義不涉及內存搬移的入棧操作為 simple-push,時間復雜度為 O ( 1 ) O(1) O(1)

如果當前棧大小為 k,并且已滿,當再有新的數據要入棧時,就需要重新申請 2 倍大小的內存,并且做 k 個數據的搬移操作,然后再入棧。

  • 我們將 k 個數據的搬移操作,均攤到前面 k 次的 simple-push 操作。
  • 均攤后,每個入棧只需要一次 simple-push 操作和 一次搬移操作。
  • 也就是說,均攤后,入棧操作的均攤時間復雜度就為 O ( 1 ) O(1) O(1)

在這里插入圖片描述

通過這個例子的實戰分析,也印制了前面講的,均攤時間復雜度一般都等于最好情況時間復雜度。因為在大部分情況下,入棧的操作時間復雜度都是 O ( 1 ) O(1) O(1),只有在個別時刻才會退化為 O ( n ) O(n) O(n),所以把好是多的入棧操作均攤到其他入棧操作上,平均情況下的耗時就接近 O ( 1 ) O(1) O(1)

棧在函數調用中的應用

接下來在看棧的另一個常見的應用場景,編譯器如何利用棧來實現表達式求值

為了方便解釋,我們將算術表達式簡化為只包含加減乘除四則運算,比如:34+13*9+44-12/3。對于這個四則運算,人腦可以很快求接觸答案,但是對于計算機來說,理解這個表達式本身就是個挺難得事兒。如果換作你,讓你來實現這樣一個表達式求值的功能,你會怎么做?

實際上,編譯器就是通過兩個棧來實現的。其中一個是保存操作數的棧,另一個是保存運算符的棧。我們從左向右遍歷表達式:

  • 當遇到數字,我們就直接壓入操作數棧;
  • 當遇到運算符,就與運算符棧的棧頂元素進行比較。
    • 如果運算符 比 運算符棧頂元素的優先級高,就將當前的運算符壓入棧;
    • 如果運算符 比 運算符棧頂元素的優先級低或者相同,從運算符中取棧頂運算符,從操作數棧的棧頂取 2 個操作數,然后進行計算,再把計算的記過壓入操作數棧,繼續比較。

我們將 3+5*8-6 這個表達式的計算過程畫了一張圖,你可以 結合圖來理解上面的計算過程。
在這里插入圖片描述

棧在括號匹配中的應用

除了用棧來實現表達式求值,還可以借助棧來檢查表達式中的括號匹配。

假設表達式中只包含三種括號,圓括號 ()、花括號 {} 和方括號 [],并且它們可以任意嵌套。比如 {[]()[{}]}[{()}([])] 等都為合法格式,而 {[}()][({)] 問哦不合法格式。那我現在給你一個包含三種括號的表達式字符串,如何檢查它們是否合法呢?

這里也可以使用棧來解決。用棧來保存未匹配的左括號,從左到右一次掃描字符串。當掃描到左括號時,將其壓入棧中;當掃描到有括號時,從棧頂取出一個左括號。如果能夠匹配,比如 () 匹配,[ 跟 ] 匹配,{} 匹配,則繼續掃描剩下的字符串。如果掃描過程中,遇到不能匹配的右括號,或者棧中沒有數據,則說明為非法格式。

當所有的括號都掃描完成之后,如果棧為空,則說明字符串為合法字符串;否則,說明有未匹配的左括號,為非法格式。

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

其實,用兩個棧就可以完美解決。

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

比如,你順序查看了 abc 三個頁面,我們依次把 abc 壓入棧 X,這個時候,兩個棧的數據就是這個樣子的。
在這里插入圖片描述
當你通過后退按鈕,從頁面 c 退到頁面 a 之后,我們就一次把 cb 從棧 X 中取出,并依次放入棧 Y。這個時候數據就是這樣的。

在這里插入圖片描述

這個時候,如果你又想看頁面 b,于是你點擊了前進按鈕回到頁面 b,我們就再把 b 從棧 Y 出棧,放入棧 X

在這里插入圖片描述
這個時候,你通過頁面 b 跳轉到新的頁面 d,頁面 c 就無法再通過前進、后退按鈕重復查看了,所以需要清空棧 Y

在這里插入圖片描述

小結

棧是一種操作受限的數據結構,只支持入棧和出棧操作。后勁先出是它最大的特點。棧既可以通過數組實現,也可以通過鏈表來實現。不管基于數組還是鏈表,入棧、出棧的時間復雜度都為 O ( 1 ) O(1) O(1)。此外,還講了一種支持動態擴容的順序棧,你需要掌握其均攤時間復雜度的分析方法。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/19874.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/19874.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/19874.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

放開了去的 ulimit

放開了去的 ulimit 放開了去的 ulimitulimit簡介臨時修改打開文件數目永久修改系統總打開句柄限制更多信息 放開了去的 ulimit ulimit簡介 對于高并發或者頻繁讀寫文件的應用程序而言,有時可能需要修改系統能夠打開的最多文件句柄數,否則就可能會出現t…

HTTPS 原理技術

HTTPS原理技術 背景簡介原理總結 背景 隨著年齡的增長,很多曾經爛熟于心的技術原理已被歲月摩擦得愈發模糊起來,技術出身的人總是很難放下一些執念,遂將這些知識整理成文,以紀念曾經努力學習奮斗的日子。本文內容并非完全原創&am…

Element-ui使用上傳時彈框選擇文件類型

實現效果 1,點擊上傳,上傳文件; 2,選擇文件; 3,彈框選擇文件類型; 4,選擇類型后確定上傳; 一,上傳 跳過; 二,定義彈框下拉框…

Coolmuster Android Assistant: 手機數據管理的全能助手

在數字化時代,智能手機不僅是通訊工具,更是個人數據的中心。隨著數據量的不斷增加,如何有效管理和保護這些數據成為了一個重要議題。Coolmuster Android Assistant應運而生,它是一款專為安卓用戶設計的綜合數據管理軟件&#xff0…

EXCEL數據透視圖中的日期字段,怎樣自動分出年、季度、月的功能?

在excel里,這個果然是有個設置的地方,修改后就好了。 點擊文件選項卡,選項,在高級里,將圖示選項的勾選給取消,然后再創建數據透視表或透視圖,日期就不會自動組合了: 這個選項只對新…

遙感圖像的深度學習的任務類型

在遙感圖像的深度學習任務中,利用深度學習技術處理和分析遙感圖像已經成為一個重要的研究方向。遙感圖像來自衛星、無人機等設備,包含了豐富的地球表面信息。以下是遙感圖像深度學習中的主要任務類型: 1. 圖像分類(Image Classif…

Flutter 中的 SliverPrototypeExtentList 小部件:全面指南

Flutter 中的 SliverPrototypeExtentList 小部件:全面指南 Flutter 是一個功能強大的 UI 框架,由 Google 開發,允許開發者使用 Dart 語言構建跨平臺的移動、Web 和桌面應用。在 Flutter 的豐富組件庫中,SliverPrototypeExtentLis…

山東理工大學第十六屆ACM程序設計競賽(同步賽)

山東理工大學第十六屆ACM程序設計競賽(同步賽) B、Q的網課 1、創建一個結構體,來保存我們要輸入的網課名和學時,并且對學時初始化為-1 2、然后w次輸入網課名,對每次輸入減去原先網課名對應學時,統計網課剩余…

關于torch.size和tensor的維度筆記

torch.Size([200, 1])和torch.Size([200])的區別是什么? torch.Size([200, 1]) 和 torch.Size([200]) 是兩個不同形狀的張量 (tensor) 大小。它們的區別如下: torch.Size([200, 1]): 這是一個2D張量,形狀是200行1列。這種形狀通常用來表示一個列向量或…

suffix-tree教程(個人總結)

背景 在計算機科學和生物信息學中,字符串處理是一個非常重要的領域。無論是搜索引擎、基因序列分析,還是壓縮算法,都離不開高效的字符串處理。傳統的字符串匹配算法,如暴力搜索、Knuth-Morris-Pratt (KMP) 算法和 Boyer-Moore 算…

Android14 WMS-IWindow介紹

IWindow是很重要的,官方介紹是API back to a client window that the Window Manager uses to inform it of interesting things happening. 也就是說是是用于WMS回調客戶端的,當窗口有一些改變時,WMS及時調用客戶端接口,讓客戶端…

Ubuntu22.04之解決:忘記登錄密碼(二百三十二)

簡介: CSDN博客專家,專注Android/Linux系統,分享多mic語音方案、音視頻、編解碼等技術,與大家一起成長! 優質專欄:Audio工程師進階系列【原創干貨持續更新中……】🚀 優質專欄:多媒…

gpt-4o api申請開發部署應用:一篇全面的指南

利用 GPT-4o API 開發創新應用:一篇全面的指南 OpenAI 的 GPT-4o 是一款集成了音頻、視覺和文本處理能力的多模態人工智能模型,它的出現代表了人工智能領域的重大進步。在本篇文章中,我們將詳細介紹如何通過 OpenAI API 使用 GPT-4o&#xf…

html中 table的 colspan和rowspan

Colspan 單元格跨越多列; Rowspan 單元格跨越多行 <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title></title> </head> <body><h4>單元格跨兩列:</h4> <table border"1"&…

藍橋杯java組-字符串輸入輸出處理

題目描述&#xff1a;字符串的輸入輸出處理。 輸入&#xff1a;第一行是一個正整數N&#xff0c;最大為100。之后是多行字符串&#xff08;行數大于N&#xff09;&#xff0c; 每一行字符串可能含有空格&#xff0c;字符數不超過1000。 輸出&#xff1a;先將輸入中的前N行字符…

云動態摘要 2024-05-31

給您帶來云廠商的最新動態&#xff0c;最新產品資訊和最新優惠更新。 最新優惠與活動 [1.5折起]年中盛惠--AI分會場 騰訊云 2024-05-30 人臉核身、語音識別、文字識別、數智人、騰訊混元等熱門AI產品特惠&#xff0c;1.5折起 云服務器ECS試用產品續用 阿里云 2024-04-14 云…

鴻蒙開發接口媒體:【@ohos.multimedia.medialibrary (媒體庫管理)】

媒體庫管理 說明&#xff1a; 該組件從API Version 6開始支持。后續版本如有新增內容&#xff0c;則采用上角標單獨標記該內容的起始版本。 發前請熟悉鴻蒙開發指導文檔&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md點擊或者復制轉到。 導入模塊 …

2.4 Docker部署JDK

2.4 Docker部署JDK jdk17部署&#xff08;自定義鏡像&#xff09; 1.在官網上下載jdk-17_linux-x64_bin.tar.gz&#xff0c;并安裝到/usr/local目錄下 cd /usr/local2.創建Dockerfile vim Dockerfile# 基于官方的Ubuntu 20.04鏡像作為基礎鏡像 FROM ubuntu:20.04# 設置環境…

【python深度學習】——大型工程項目管理以及互相導入

【python深度學習】——大型工程項目管理以及互相導入 1. 工程項目中常見的文件組織形式2. python中的“包”、“模塊”、與__init__.py2.1 概念理解2.2 \__init__py的使用3. 包的導入——相對導入與絕對導入3.1 相對導入3.1.1 相對導入的語法3.1.2 相對導入的使用注意事項與常…

Attentive Transfer Entropy to Exploit Transient Emergence of Coupling Effect

本文可以采用以下六個標準: 目標:指的是研究的基本目的。 耦合網絡重建專注于揭示網絡中變量之間潛在的連接結構,確定它們是如何相互關聯的。因果發現更進一步,不僅識別連接,還確定變量之間的因果關系和方向。信息傳遞測量量化變量之間流動的信息量,表明它們影響的強度和…