【資料結構與演算法】之棧的基本介紹及其陣列、連結串列實現---第四篇
一、棧的基本介紹
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