08 | 棧:如何實現瀏覽器的前進和後退功能?(☆)

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

 

如何實現一個“棧”?

注意:從數組中刪除的操作 ,其實並不需要真正的把某個值刪除,只要把我們訪問下標給減一就可以了。

class Stack {
public:
	int n;
	int count;
	int *data;
	Stack(int num) :n(num),count(0) {
		data = new int[n];
	}
	~Stack(){
		delete data;
	}
	bool push(int k) {
		if (n == count) {
			return  false;
		}
		data[count++] = k;
		return true;
	}
	int pop() {
		if (count == 0)	return 0;
		int t = data[count-1];
		count--;
		return t;
	}
};

int main() {
	return 0;
}

支持動態擴容的順序棧

當數組空間不夠時,我們就重新申請一塊更大的內存,將原來數組中數據統統拷貝過去。這樣就實現了一個支持動態擴容的數組。如果要實現一個支持動態擴容的棧,我們只需要底層依賴一個支持動態擴容的數組就可以了。當棧滿了之後,我們就申請一個更大的數組,將原來的數據搬移到新數組中。

 

支持動態擴容的棧的push、pop時間複雜度

根據均攤法,前k次入棧直接push,第k+1次入棧,需要把前面k個數據全部進行搬移,所以均攤下到前面k次,每次入棧實際上 是一次直接入棧和一次數據搬移,時間複雜度:O(1)。

疑問:數據搬移的時間複雜度是O(1)嗎?

 

  • 棧在函數調用中的應用

每調用一個函數,OS都會分配一塊棧空間,用來存儲函數調用時的臨時變量。

疑問:糾結一個問題,當調用一個函數時候,在棧上開闢一塊空間,如果再調用一個函數,那麼再開闢一個棧空間,兩個空間之間怎麼區別開呢?並且在函數中,如果連續兩個局部變量存儲,它們之間的位置關係是連續的嗎?看了下面博客懂了一些,不過還是買到這本書再把這點弄清楚吧。 

https://www.jianshu.com/p/b666213cdd8a

  • 棧在表達式求值中的應用

編譯器就是通過兩個棧來實現的。其中一個保存操作數的棧,另一個是保存運算符的棧。我們從左向右遍歷表達式,當遇到數字,我們就直接壓入操作數棧;當遇到運算符,就與運算符棧的棧頂元素進行比較。如果比運算符棧頂元素的優先級高,就將當前運算符壓入棧;如果比運算符棧頂元素的優先級低或者相同,從運算符棧中取棧頂運算符,從操作數棧的棧頂取 2 個操作數,然後進行計算,再把計算完的結果壓入操作數棧,繼續比較。

  • 棧在括號匹配中的應用

 

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

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

比如你順序查看了 a,b,c 三個頁面,我們就依次把 a,b,c 壓入棧,這個時候,兩個棧的數據就是圖(1);

當你通過瀏覽器的後退按鈕,從頁面 c 後退到頁面 a 之後,我們就依次把 c 和 b 從棧 X 中彈出,並且依次放入到棧 Y。這個時候,兩個棧的數據就是圖(2);

這個時候,你通過頁面 b 又跳轉到新的頁面 d 了,頁面 c 就無法再通過前進、後退按鈕重複查看了,所以需要清空棧 Y。此時兩個棧的數據就是圖(3)。

 

課後思考:

爲什麼函數調用要用“棧”來保存臨時變量呢?

其實,我們不一定非要用棧來保存臨時變量,只不過如果這個函數調用符合後進先出的特性,用棧這種數據結構來實現,是最順理成章的選擇。
從調用函數進入被調用函數,對於數據來說,變化的是什麼呢?是作用域。所以根本上,只要能保證每進入一個新的函數,都是一個新的作用域就可以。而要實現這個,用棧就非常方便。在進入被調用函數的時候,分配一段棧空間給這個函數的變量,在函數結束的時候,將棧頂復位,正好回到調用函數的作用域內。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章