棧
棧(stack)又名堆疊,它是一種運算受限的線性表。限定僅在表尾進行插入和刪除操作的線性表。這一端被稱為棧頂,相對地,把另一端稱為棧底。向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素;從一個棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。
下面就用計算器作為示例進行講解。
計算器需求
如上圖:輸入一個表示式 7*2*2-5+1-5+3-3
,然後計算出他的結果。
問:計算機底層是如何運算得到結果的?對於計算機而言他接受到的是一個 字串,怎麼計算出來的?
針對這個問題,我們討論的就是 棧
棧介紹
stack 棧,是一個 先入後出(FILO,First In Last Out)的 有序列表。
是限制 線性表 中元素的插入和刪除只能線上性表的 **同一端 **進行的一種特殊線性表:
- 棧頂(Top):允許插入和刪除的一端,為 變化的一端。稱為棧頂
- 棧底(Bottom):另一端為 固定的一端,稱為棧底
根據上述定義,可知:
- 最先 放入棧中元素在 棧底
- 最後 放入棧中元素在 棧頂
而刪除元素則剛好相反:
- 最先 放入棧中元素,最後 刪除
- 最後 放入棧中元素,最先 刪除
可以參考下圖的,入棧和出棧圖示:
棧的應用場景
-
子程式的呼叫
在跳往子程式前,會先將 下個指令的地址 存到堆疊中,直到子程式執行完後再 將地址取出,以 回到原來的程式中。
如方法中呼叫方法。
-
處理遞迴呼叫
和子程式呼叫類似,只是除了儲存下一個指令的地址外,也將引數、區域變數等資料存入堆疊中。
-
表示式的轉換(中綴表示式轉字尾表示式)與求值(實際解決)
-
二叉樹的遍歷
-
圖形的深度優先(depth-first)搜尋法
陣列模擬棧
參考前面的入棧和出棧的圖,思路如下:
- 定義一個陣列,來模擬棧
- 定義一個 top 變數表示棧頂,初始化為
-1
- 入棧:
stack[++top]=data
- 出棧:
return stack[top--]
/**
* 陣列模擬棧
*/
//定義一個 ArrayStack 表示棧
class ArrayStack {
private int maxSize; // 棧的大小
private int[] stack; // 陣列,陣列模擬棧,資料就放在該陣列
private int top = -1;// top表示棧頂,初始化為-1
//構造器
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
//棧滿
public boolean isFull() {
return top == maxSize - 1;
}
//棧空
public boolean isEmpty() {
return top == -1;
}
//入棧-push
public void push(int value) {
//先判斷棧是否滿
if(isFull()) {
System.out.println("棧滿");
return;
}
top++;
stack[top] = value;
}
//出棧-pop, 將棧頂的資料返回
public int pop() {
//先判斷棧是否空
if(isEmpty()) {
//丟擲異常
throw new RuntimeException("棧空,沒有資料~");
}
int value = stack[top];
top--;
return value;
}
//顯示棧的情況[遍歷棧], 遍歷時,需要從棧頂開始顯示資料
public void list() {
if(isEmpty()) {
System.out.println("棧空,沒有資料~~");
return;
}
//需要從棧頂開始顯示資料
for(int i = top; i >= 0 ; i--) {
System.out.printf("stack[%d]=%d\n", i, stack[i]);
}
}
}
測試用例
public class ArrayStackTest {
@Test
public void pushTest() {
ArrayStack stack = new ArrayStack(4);
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.list();
stack.push(5);
}
@Test
public void popTest() {
ArrayStack stack = new ArrayStack(4);
stack.push(1);
stack.push(2);
stack.list();
System.out.println("pop 資料:" + stack.pop());
stack.list();
System.out.println("pop 資料:" + stack.pop());
stack.list();
}
}
輸出資訊
====== pushTest ======
stack[3]=4
stack[2]=3
stack[1]=2
stack[0]=1
棧滿
====== popTest
stack[1]=2
stack[0]=1
pop 資料:2
stack[0]=1
pop 資料:1
棧空,沒有資料~~
連結串列模擬棧
思路:
- 首先定義一個連結串列節點類
- 想的是怎麼壓入資料? 根據棧的特點,可以用頭插法來新增連結串列的節點,實現先入後出。
- 定義一個連結串列棧類
- 入棧
push
通過頭插法進行插入節點 - 出棧
pop
通過刪除連結串列的第一個節點 - 展示棧內資料,遍歷連結串列即可
- 入棧
連結串列節點類
class node{
private int data;//資料域
private node next = null;//下一個節點,預設為空
public node(int data) {
this.data = data;//設定資料
}
public int getData() {
return data;
}
public node getNext() {
return next;
}
public void setNext(node next) {
this.next = next;
}
//為了列印方便重寫toString
@Override
public String toString() {
return data + "";
}
}
連結串列棧類
class LinkedListStack {
private int maxSize;//棧的最大容量
private int size;//用來記錄棧中資料個數
private node head = new node(0);//頭節點
private node helper;//定義一個輔助變數用來幫助實現頭插法
public LinkedListStack(int maxSize) {
this.maxSize = maxSize;//設定棧的最大容量
}
//判斷是否滿棧
public boolean isFull() {
return size == maxSize;
}
//判斷是否空棧
public boolean isEmpty() {
return size == 0;
}
//入棧
public void push(int data) {
//判斷是否滿棧
if (isFull()) {
System.out.println("棧滿");
return;
}
//建立新節點
node node = new node(data);
//入棧
if (size == 0) {
head.setNext(node);
helper = node;//將輔助變數指向新新增的節點
size++;
} else {
head.setNext(node);//將頭節點的next指向新的節點
node.setNext(helper);//將新的節點的next指向久節點
helper = node;//再將輔助變數指向新節點
size++;
}
}
//出棧
public int pop() {
//判斷是否是空棧
if (isEmpty()) {
throw new RuntimeException("棧空,沒有資料~~");
}
//獲取第一個節點的資料,因為是頭插法,所以滿足棧的性質
int data = head.getNext().getData();
//將第一個節點刪除
head.setNext(helper.getNext());//將頭節點的next指向出棧節點的下一個節點
helper = helper.getNext();//將輔助變數移動到出棧節點的下一個節點
size--;
return data;
}
//顯示棧的情況[遍歷棧], 遍歷時,需要從棧頂開始顯示資料
public void list() {
if (isEmpty()) {
System.out.println("棧空,沒有資料");
return;
}
//設定輔助變數,來遍歷連結串列
node temp = helper;//helper就是指向連結串列中第一個節點
for (int i = size - 1; i >= 0; i--) {
System.out.printf("stack[%d]=%d\n", i, temp.getData());
temp = temp.getNext();
}
}
}
測試用例
/**
* 連結串列實現棧
*/
public class test {
public static void main(String[] args) {
//============push========================
LinkedListStack linkedListStack = new LinkedListStack(4);
System.out.println("================push==================");
linkedListStack.push(1);
linkedListStack.push(2);
linkedListStack.push(3);
linkedListStack.push(4);
linkedListStack.list();
linkedListStack.push(5);
//============pop========================
System.out.println("==================pop====================");
System.out.println(linkedListStack.pop());
System.out.println(linkedListStack.pop());
linkedListStack.list();
System.out.println(linkedListStack.pop());
System.out.println(linkedListStack.pop());
linkedListStack.list();
try {
System.out.println(linkedListStack.pop());
} catch (Exception e) {}
}
}
測試輸出
================push==================
stack[3]=4
stack[2]=3
stack[1]=2
stack[0]=1
棧滿
==================pop====================
4
3
stack[1]=2
stack[0]=1
2
1
棧空,沒有資料