我是野豬。
棧的概念:棧作爲一種數據結構,是一種只能在一端插入和刪除操作的特殊線性表。它按照新進後出的原則存儲數據,先進入的數據被壓入棧底,最後的數據在棧頂,需要讀數據的時候從棧頂開始彈數據。
關鍵點:1.先入後出 2.訪問限制,在特定的時刻,只允許一個元素被讀取或刪除(棧頂的元素)。3.棧操作:棧空、棧滿、彈棧、入棧等
棧相關知識:
1.數據結構棧和隊列更多的是作爲程序員的工具來運用。他們主要作爲構思算法的輔助工具,而不是完全的數據存儲工具。這些數據結構的生命週期比那些數據庫類型的結構要短暫的多。在程序執行期間他們才被創建,通常用他們去執行某項特殊的任務,當任務完成之後,他們就被銷燬。
2.棧的主要機制可以通過數組來實現,也可以通過鏈表來實現。優先級隊列的內部實現可以通過數組或堆來實現。
3.實際計算機系統中,大部分微處理器運用棧的體系結構。棧在程序的運行中有舉足輕重的作用。最重要的是棧保存了一個函數調用時所需要的維護信息,這常常稱之爲堆棧幀。堆棧幀一般包含下面幾個信息:①函數的返回地址和參數 ②臨時變量:包含函數的非靜態局部變量以及編譯器自動生成的其他臨時變量。當調用一個方法時,把他的返回地址和參數壓入棧,當方法結束返回時,那些數據出棧。棧操作就嵌入在微處理器中。
4.棧通常很小,是臨時的數據結構。
爲了更好理解數據結構棧,我們根據棧的屬性,編寫自己的棧,並且根據棧先入後出的原則實現了兩個最基本的例子:字符串反轉和分隔符匹配問題。
根據性質編寫自己的棧:
/**
* 創建一個自己的棧
* 1.先入後出的原則
* 2.訪問權限 在特定時刻只有一個數據項可以讀取或者被刪除 只能操作位於棧頂的元素
* 組織數據的方式 入棧 出棧 棧空 棧滿
*/
public class YezhuStack {
//類型爲Object的數組
private final Object[] oStack;
//數組的長度
private int size;
//棧頂標誌
private int top = -1;
public YezhuStack(int max) {
this.size = max;
oStack = new Object[max];
}
/**
* 入棧
* 先判斷是不是棧滿的
*
* @param object
*/
public void push(Object object) {
if (!isFull()) {
oStack[++top] = object;
} else {
throw new IndexOutOfBoundsException("the stack is full");
}
}
/**
* 棧滿否
*
* @return
*/
private boolean isFull() {
return top == size - 1;
}
/**
* 出棧
* 出棧先判斷棧是不是爲空
* 訪問權限:在特定時刻只有一個數據項被允許讀取或刪除
*
* @return
*/
public Object pop() {
if (!isEmpty()) {
return oStack[top--];
} else {
throw new IndexOutOfBoundsException("the stack is empty");
}
}
/**
* 棧是不是空
*
* @return
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 查看棧頂的元素
*/
public Object peek() {
if (!isEmpty()) {
return oStack[top];
} else {
throw new IndexOutOfBoundsException("the stack is empty");
}
}
}
注意點:初始值top=-1,是根據代碼做的修改。push時oStack[++top],++top =>先+1後賦值;pop時,oStack[top--],top-- => 先賦值後-1;還有判斷棧是否爲空,條件是top==-1而不是size=0,因爲一開始初始化的時候根據要操作的數據的長度創建的棧,而在壓棧和彈棧期間,其size並沒有變化。
例子1:字符串反轉;字符串反轉就是輸入一字符串,把字符串中的一個個元素從頭壓入棧,然後彈出棧時就是倒序。
/**
* 測試字符串反轉
*/
public class ReverseStrTest {
private static YezhuStack yezhuStack;
public static void main(String[] args) throws IOException {
System.out.println("please import str:");
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(inputStreamReader);
String str = br.readLine();
System.out.println("print reversed str:" + reverseStr(str));
}
/**
* 字符串反轉後
*
* @param str
*/
private static String reverseStr(String str) {
String out = "";
if (str != null) {
yezhuStack = new YezhuStack(str.length());
}
//1.字符串放入棧中
for (int i = 0; i < str.length(); i++) {
yezhuStack.push(str.charAt(i));
}
//2.棧直接輸出即可
for (int i = 0; i < str.length(); i++) {
out = out + yezhuStack.pop();
}
return out;
}
}
運行效果:
例子2:分隔符匹配;分隔符匹配程序從字符串中不斷的讀取字符,每次讀取一個字符。若發現他是左分隔符,將他壓入棧。當從輸入中讀取一個右分隔符,彈出棧頂的左分隔符,並且查看是否和右分隔符相匹配。比如eclipes編譯器,當你少一個“}”時,會報錯,其原理就是如此。
/**
* 分隔符匹配問題
* 用棧結構模擬的分隔符匹配;
* 編譯器報錯的 比如當少{[( )]} 左分隔符 壓入棧,遇到右分隔符時 與棧頂元素進行匹配, 不匹配則編譯器報錯
*/
public class SeparatorMatchTest {
private static YezhuStack yezhuStack;
public static void main(String[] args) throws IOException {
System.out.println("please import str:");
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(inputStreamReader);
String str = br.readLine();
checkSeparator(str);
}
/**
* 檢查輸入的字符串
*
* @param str
*/
private static void checkSeparator(String str) {
if (str != null) {
yezhuStack = new YezhuStack(str.length());
}
//1.整個字符串中遇到左匹配符號就入棧 因此循環入棧的時候要匹配條件
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
switch (ch) {
case '{':
case '(':
case '[':
//以上是左匹配符 遇到左匹配符入棧
yezhuStack.push(ch);
break;
case '}':
case ')':
case ']':
//遇到右匹配符然後出棧 做對比
if (!yezhuStack.isEmpty()) {
char chRight = (char) yezhuStack.pop();
if ((ch == '}' && chRight != '{')
|| (ch == '(' && chRight != ')')
|| (ch == ']' && chRight != '[')) {
//ch是循環拿字符串中匹配符 chRight是棧中的左匹配符
System.out.println("Error: " + ch + " at " + i);
}
}
break;
default:
break;
}
}
//最後做一次判斷 正常來說要是左右分隔符能全部匹配成功的話 此時棧中已經空了 如果不爲空 說明右分隔符少了
if (!yezhuStack.isEmpty()) {
System.out.println("輸入含有左右分隔符的字符串缺少右分隔符");
}
}
}
運行效果:
以上就是兩個基本使用棧的例子,如果有問題可以私信我。
補充在java基礎講過的棧和堆內存的概念:
1.棧內存用來存儲局部變量和方法調用,堆內存用來存儲java中new出來的對象。
2.棧內存屬於單個線程,每個線程都會有一個棧內存,其存儲的變量只能在其所屬線程中可見;而堆內存中的對象對所有線程可見,可以被所有線程訪問。