1. 程式人生 > >【資料結構與演算法】之棧的基本介紹及其陣列、連結串列實現---第四篇

【資料結構與演算法】之棧的基本介紹及其陣列、連結串列實現---第四篇

一、棧的基本介紹

1、棧的基本概念

棧是一種限制在一端進行插入和刪除操作的線性表資料結構。棧中有兩個比較重要的操作:push(壓棧:將元素壓入棧頂)和pop(彈棧:從棧頂彈出一個元素)。都滿足先進後出、後進先出的特點!

從圖中可以看出,我們常把棧的上面稱為棧頂,棧頂的第一個元素被稱為棧頂元素,相對地,把另外一段稱為棧底。向一個棧中插入新元素又稱為壓棧或者進棧,它是把該元素放到棧頂位置上面,使之成為棧頂元素。那麼從一個棧中刪除一個元素則被稱之為彈棧或者出棧,它會把棧頂元素刪除,使其下面的相鄰元素成為新的棧頂元素。

雖然棧是一種受限的資料結構,其操作特性均可以用陣列和連結串列實現。但是任何資料結構都是對特定應用場景的抽象,陣列和連結串列雖然靈活,但是卻暴露了幾乎所有的操作,很容易引發操作失誤的風險。所以當某個資料集合只涉及到固定端的插入和刪除操作,且滿足先進後出,後進先出的特點,那麼我們就應該優先考慮使用棧這種資料結構。

其實生活中有很多棧的例子,比如:疊放的盤子:我們要放盤子的時候,只能放在最上面。同時,需要拿盤子的時候,也只能拿最上面的。

2、棧的儲存結構

棧是一種線性儲存結構,所以線性表的順序儲存(陣列)鏈式儲存(連結串列)都可以實現棧。下面會具體用這兩種方式對棧進行實現。

不管是順序棧還是鏈式棧,壓棧和彈棧都只涉及棧頂個別元素的操作,所以其時間複雜度均為O(1)。

3、棧的應用

棧在實際程式應用中有很多場景,下面列舉幾個比較常見的:

(1)棧在二進位制表示式求值中的應用

在此類場景中,利用兩個棧,一個棧用來儲存運算元,另外一個用來儲存運算子。我們從左到右遍歷表示式,當遇到數字,我們就直接壓入運算元棧;當遇到運算子,就與運算子棧的棧頂元素進行比較,若比運算子棧的棧頂元素優先順序高,就將當前運算子壓入運算子棧,若比運算子棧的棧頂元素優先順序低或者相等,則從運算子棧取出棧頂運算子,從運算元棧中取出兩個運算元,然後進行計算,把計算完的結果再壓入運算元棧,然後依次進行比較。

圖片來自極客時間的《資料結構與演算法之美》專欄

(2)棧在符號號匹配中的應用

比如:括號匹配:{  [()]  })、單引號和雙引號的匹配

拿括號匹配舉例:從左到右依次掃描字串,當掃描到左括號時,則將其壓入棧中;當掃描到右括號時,則取出棧頂元素中的左括號,如果能夠匹配上,則繼續掃描;如果不匹配,則說明為非法格式。當所有的括號掃描完成之後,如果棧為空,則說明匹配成功,否則為非法格式。

(3)棧在瀏覽器中網頁的前進後退中的應用

使用A和B兩個棧,我們把首次瀏覽的頁面依次壓入棧A(比如:網頁1-->網頁2-->網頁3),當點選後退按鈕時,再依次從棧A中出棧,並按出棧的順序將它們壓入棧B。所有當我們點選前進按鈕時,我們就可以依次從棧B中取出資料放入棧A中了。需要說明的是如果在前進後退期間,點開了新的頁面,則棧B清空。當棧A中沒有資料時,說明頁面不可以後退瀏覽了,相反當棧B中沒有元素的時候,頁面不可以前進瀏覽了。

二、棧的實現(Java實現)

1、陣列實現棧

public class ArrayStack {
	
	private Object[] array;     // 陣列
	private int count;       // 棧中元素的個數
	private int n;           // 棧的大小
	
	// 初始化陣列,申請一個大小為n的陣列空間
	public ArrayStack(int n){
		this.array = new Object[n];
		this.count = count;
		this.n = n;
	}
	
	// 壓棧
	public boolean push(Object value){
		// 如果陣列的空間不夠了,直接返回false,入棧失敗
		if(count == n){
			return false;
		}
		// 將value壓入棧頂,即下標為count的位置
		array[count] = value;
		count++;
		return true;
	}
	
	// 彈棧
	public Object pop(){
		// 棧為空,則直接返回-1
		if(count == 0){
			return null;   
		}
		// 返回棧頂的元素,即下標為count-1的陣列元素
		Object obj = array[count - 1];
		count--;
		return obj;
	}
	
	// 返回彈中最近新增的元素,而不刪除它
	public Object peek(){
		return array[n-1];
	}
	
	// 顯示棧中的資料
	public void display(){
		for(int i = 0; i < count; i++){
			System.out.print(array[i] + ", ");
		}
	}
	
	// 棧中元素的個數
	public int size(){
		return count;
	}
	
	// 判斷棧中元素是否為空
	public boolean isEmpty(){
		return count == 0;
	}
	
}

測試程式碼:

public class ArrayStackTest {

	public static void main(String[] args) {
		
		ArrayStack stack = new ArrayStack(5);
		
		boolean isEmpty = stack.isEmpty();
		System.out.println(isEmpty);
		
		// 壓棧
		stack.push("a");
		stack.push("b");
		stack.push("c");
		stack.push("d");
		stack.push("e");
		
		// 顯示
		stack.display();
		
		System.out.println();
		// 彈棧
		stack.pop();
		stack.pop();
		stack.pop();
		stack.display();
		
		int size = stack.size();
		System.out.println(size);
		
	}
	
}

2、連結串列實現棧

public class LinkStack {

	private class Node{
		Object data;
		Node next;
	}
	
	private Node first;   // 定義頭結點  
	private int n = 0;        // 連結串列中元素的個數
	
	public LinkStack(){
		
	}
	
	// 壓棧
	public void push(Object data){
		Node oldFirst = first;
		first = new Node();
		first.data = data; 
		first.next = oldFirst;  // 將新的頭結點的後繼指標指向老的頭結點
		n++;
	}
	
	// 彈棧
	public Object pop(){
		Object data = first.data;
		first = first.next;    // 將頭結點的後一個結點設為頭結點
		n--;
		return data;
	}
	
	// 返回棧中最近新增的元素而不是刪除它
	public Object peek(){
		return first.data;
	}

	// 顯示棧中的元素
	public void display(){
		for(int i = 0; i < n; i++){
			Object obj = peek();
			System.out.print(obj + ", ");
		}
	}
	
	// 棧中元素的數量
	public int size(){
		return n;
	}
	
	// 判斷棧是否為空
	public boolean isEmpty(){
		return n == 0;
	}
	
}

測試程式碼:

public class LinkStackTest {

	public static void main(String[] args) {

		LinkStack stack = new LinkStack();

		boolean isEmpty1 = stack.isEmpty();
		System.out.println(isEmpty1);
		
		// 壓棧
		stack.push("A");
		stack.push("B");
		stack.push("C");
		stack.push("D");
		stack.push("E");

		// 顯示棧中的元素
		stack.display();
		int size1 = stack.size();
		System.out.println(size1);

		// 彈棧
		stack.pop();
		stack.pop();
		stack.pop();

		// 顯示棧中的元素
		stack.display();
		int size2 = stack.size();
		System.out.println(size2);
		
		boolean isEmpty2 = stack.isEmpty();
		System.out.println(isEmpty2);

	}

}

在極客時間的《資料結構與演算法之美》專欄裡有個問題,感覺值得mark:

問題:JVM記憶體管理中的棧(用來儲存區域性變數和方法呼叫的)和這裡的棧相同嗎?

參考及推薦

2、棧和佇列

學習不是單打獨鬥,如果你也是做Java開發,可以加我微信,一起分享經驗學習!

本人微訊號:pengcheng941206