1. 程式人生 > >棧:如何實現瀏覽器的前進和後退功能?

棧:如何實現瀏覽器的前進和後退功能?

本文是學習演算法的筆記,《資料結構與演算法之美》,極客時間的課程

我們瀏覽網頁,經常會用到前進,後退的功能。比如依次瀏覽了a-b-c 三個頁面,這時點後退,可以回到 b 頁面,再次點選回到 a 頁面。如果這時,又進入 新頁面d ,那就無法通過前進,後退功能回到b、c頁面了。

這樣一個功能,要如何實現呢?

其實這樣一個功能,可以藉助棧來實現。

什麼是棧?它是一個數據結構,資料先進後出,後進先出。打一個比方,放一疊盤子,放的時候,都放最上面,取的時候也從最上面取。典型的後進先出。

棧,可以用陣列來實現,也可以用連結串列來實現。前者叫順序棧,後者是鏈式棧。

為什麼會有棧這種資料結構,直接用陣列或是連結串列不就好了麼?棧有嚴格的限制,只能從棧頂壓入資料,從棧頂取資料,可以滿足特定場景的需求,而連結串列或是陣列暴露了太多的介面,容易出錯。

如何實現一個棧

public class ArrayStack{
	private String [] items; // 陣列
	private int count; // 棧中元素個數
	private int n; //棧的大小
	
	// 初始化陣列,申請一個大小為 n 的陣列空間
	public ArrayStack(int n){
		items = new String [n];
		this.n = n;
		count = 0;
	}
	// 入棧操作
	public boolean push(String item){
		if(count == n){
			return false;
		}
		items[count-1] = item;
		count++;
		return true;
	}
	// 出棧操作
	public String pop(){
	if(count == 0){
		return null;
	}
	String temp = items[count-1];
	count--;
	return temp;
	}
}

棧的實際應用

棧作為一個基礎的資料結構,經典的應用場景是函式呼叫。

我們知道,作業系統,為每個執行緒分配了獨立的記憶體空間,這些記憶體空間組織成棧的結構,來儲存函式呼叫產生的臨時變數。每次進入一個函式,將臨時變數作為棧楨入棧,當函式呼叫完成,有結果返回時,函式對應的棧楨出棧。便於理解,我們看一看下面的一段程式碼

int main() {
	int a = 1;
	int ret = 0;
	int res = 0;
	ret = add(3, 5);
	res = a+ret;
	printf("%d", res);
	return 0;
}
int add(int x, int y){
	int sum = 0;
	sum = x + y;
	return sum;
}

為了清晰地看到這個函式呼叫的過程中,出棧、入棧的操作,貼一張圖
在這裡插入圖片描述

棧在表示式求值中的應用

再看一個常見的應用場景,編譯器如何利用棧來實現表示式求值。

我們用一個簡單的四則運算來說明這個問題,比如3+5*8-6,對於計算機來說,如何理解這個表示式,是個不太容易的事兒。

事實上,編譯器是通過兩個棧來實現的,其中一個儲存數字,暫且叫A,一個儲存運算子暫且叫B。從左到右遍歷表示式,當遇到數字,直接壓入A棧,當遇到運符時,就與B棧中棧頂元素進行比較。

如果比棧頂元素的優先順序高,就將運算子壓入棧,如果相同或者低,從B棧中取出棧頂元素,從A棧的棧頂取出兩個數字,進行計算,再把計算結果壓入A棧,繼續比較。如下圖在這裡插入圖片描述

解答開篇的那個問題

我們使用兩個棧,X和Y,首次瀏覽的頁面,依次壓入棧X。當點選回退時,依次從X棧中取出資料,並依次放入Y棧。點選前進時,從Y棧中取出資料,並依次放入X棧。當X棧中沒有資料時,就說明不能再回退了;當Y中沒有資料時,就說明不能再前進了。

比如你依次瀏覽了a b c 三個頁面,我們依次把 a b c 壓入棧,這時兩個棧是這個樣子
在這裡插入圖片描述
瀏覽器通過後退按鈕,回退到頁面 a 時,我們把 c b 依次壓入Y棧,這時兩個棧是這個樣子
在這裡插入圖片描述
這時候,你又通過前進按鈕,又回到 b 頁面,這時兩個棧的資料是這個樣子
在這裡插入圖片描述

這時,你由 b 頁面開啟新的頁面 d,那麼你就不能再通過後退回到 c 頁面,Y棧資料要清空。這時兩個棧是這個樣子
在這裡插入圖片描述