棧(Java)
棧
前言:博主第一次寫部落格,用詞或一些概念上可能會出現一些錯誤,希望大家指出來。
下壓棧(LIFO)
下壓棧(簡稱棧)是一種具有後進先出特點的集合了型別。 棧的應用性很廣泛,使用棧可以解決括號匹配問題,迷宮問題,表示式求值這類問題。
我們先用圖形來解釋一下什麼是“棧”吧。 首先我們假設這裡有一個空箱子(空棧),我們手上有3本書,Java,C++,以及PHP。
如果我們想將一本java書放入,需要怎麼做呢? 其實非常簡單,直接放進入就好了。(壓棧,也叫入棧) 當我們依次再將C++、PHP的書放進去,就會形成下面的狀態。 我們已經成功的將三本書放入,接下來我們需要考慮如何取出目標書籍。
比如:當我們需要Java書時,該如何做呢? 我們發現,此時Java是在箱子底部,而我們只能取出箱子頂部(棧頂)的書(元素)。所以我們想取出Java書,就必須將PHP與C++取出。而C++又在PHP的下面,所以我們的第一步就是將箱子頂部的PHP取出(彈棧,也叫出棧)。
經過分析,我們需要從箱子頂部(棧頂)取出第一本書(棧頂元素)。取出後,C++書就到了箱子頂部(棧頂),而Java依舊在箱子底,所以我們還需要一次取出箱子頂部的書(棧頂元素)。
接著上一步,我們需要從箱子頂部(棧頂)再次取出第一本書(棧頂元素)。取出後,Java書就到了箱子頂部(棧頂)。 此時我們只需從箱子裡拿出Java即可。(彈棧)
根據上述過程,我們很直觀的發現了棧的特點:後進先出(LIFO,Last In First Out),也可稱作先進後出(FILO)。
順序表實現棧
棧的API
下壓棧(FILO) | |
---|---|
public class Stack<Item> | |
Stack() | 建立一個空棧 |
void push(Item data) | 壓棧 |
Item pop() | 彈棧 |
boolean isEmpty() | 判斷棧是否為空 |
int size() | 棧中元素數量 |
void resize(int max) | 修改棧大小 |
棧類中成員變數:
為了不被外部類所修改,棧中成員變數我們用private關鍵字來進行封裝:
private Item[] a = (Item[]) new Object[1];//由於某種原因,建立泛型陣列不被允許,因此需要進行型別轉換
private int N = 0;//標記棧頂,同樣也儲存了棧的大小
方法的實現
1.Stack()
public Stack() { }
2.size()
public int size() {
return N;
}
3.boolean isEmpty()
public boolean isEmpty() {
return N == 0;
}
4.void push(Item data)
public void push(Item data) {
if (a.length == N) {//棧滿,將棧長修改為原長2倍
resize(a.length * 2);
}
a[N++] = data;//壓棧
}
5.Item pop()
public Item pop() {
Item data = a[--N];//彈棧,並縮短棧的大小
a[N] = null;//將原棧頂賦null,防止物件遊離
if (N > 0 && N == a.length / 4)//順序表利用率低於25%,縮短順序表長度
resize(a.length / 2);
return data;//返回原棧頂元素
}
6.void resize(int max)
public void resize(int max) {
Item[] temp = (Item[]) new Object[max];//建立新順序表
for (int loop = 0 ; loop < N ; loop++)//將原表資料儲存至新表
temp[loop] = a[loop];
a = temp;//重新指向
}
這裡需要說明一下:由於使用順序表實現棧,順序表長度就得因棧的大小而改變。所以添加了一個名為resize的方法來重置順序表長度。當順序表利用率低於25%時,順序表主動縮小。
整體程式碼如下:
public class Stack<Item> {
private Item[] a = (Item[]) new Object[1];//由於某種原因,建立泛型陣列不被允許,因此需要進行型別轉換
private int N = 0;//標記棧頂,同樣也儲存了棧的大小
public Stack() { }
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
public void resize(int max) {
Item[] temp = (Item[]) new Object[max];//建立新順序表
for (int loop = 0 ; loop < N ; loop++)//將原表資料儲存至新表
temp[loop] = a[loop];
a = temp;//重新指向
}
public void push(Item data) {
if (a.length == N) {//棧滿,將棧長修改為原長2倍
resize(a.length * 2);
}
a[N++] = data;//壓棧
}
public Item pop() {
Item data = a[--N];//彈棧,並縮短棧的大小
a[N] = null;//將原棧頂賦null,防止物件遊離
if (N > 0 && N == a.length / 4)//順序表利用率低於25%,縮短順序表長度
resize(a.length / 2);
return data;//返回原棧頂元素
}
}
連結串列實現棧
連結串列
連結串列是一種遞迴的資料結構,它或者為空(null),或者是指向一個結點(node)的引用,該結點含有一個泛型的元素和一個之相關另一條連結串列的引用。 –摘自《Algorithms》第四版(Robert Sedgewick & Kevin Wayne)
下面我們依舊使用圖示來理解。
首先一個結點,擁有兩個區域,一個儲存資料,一個儲存指向。
我們先使用一個結點的next域儲存另一個結點,那麼就得到了一個鏈式儲存單元,前一個結點的next域指向下一個結點。
當我們將最後一個結點的next域指向null,我們就得到了一個連結串列。
連結串列結點的定義
private class Node {
Item data;
Node next;
public Node() {//建立結點
}
public Node(Item data, Node next) {//建立指定資料以及指向的結點
this.data = data;
this.next = next;
}
}
鏈式棧的實現原理
首先建立一個結點,並賦值5,此時該結點就是棧頂元素,用First指向它。
此時再進入一個2,那麼棧頂元素更新,變為2,First也就重新指向了2。
此時再存入一個3,操作與上面的方法一致。
彈棧時,則只需將頭結點彈出,First指向2(棧頂指向新的結點),此時就恢復到了第二步。
也就是說,First一直在更新為棧頂元素。 在你將一個元素壓棧,First也就指向你的新結點,而原來的棧頂結點則被新結點的next儲存。 在你將一個元素彈棧,First也就指向你的新結點,而原來的站定結點則被彈出並返回。
這就是鏈式棧的實現原理。
棧的API
下壓棧(FILO) | |
---|---|
public cllass LinkStack<Item> | |
LinkStack() | 建立一個空棧 |
void push(Item data) | 壓棧 |
Item pop() | 彈棧 |
boolean isEmpty() | 判斷棧是否為空 |
int size() | 棧中元素數量 |
1.LinkStack()
public LinkStack() { }
2.int size()
public int size() {
return N;
}
3.boolean isEmpty()
public boolean isEmpty() {
return first == null;
}
4.void push(Item data)
public void push(Item data) {
Node oldFirst = first;//儲存原來的棧頂結點
first = new Node();//構建新的棧頂結點
first.data = data;//壓棧
first.next = oldFirst;//棧頂結點指向舊棧頂結點
N++;//棧的大小變大
}
5.Item pop()
public Item pop() {
Item data = first.data;//彈棧
first = first.next;//棧頂變化
N--;//棧的大小縮小
return data;
}
完整程式碼如下:
public class LinkStack<Item>{
private Node first;
private int N;
public LinkStack() { }
private class Node {
Item data;
Node next;
public Node() {//建立結點
}
public Node(Item data, Node next) {//建立指定資料以及指向的結點
this.data = data;
this.next = next;
}
}
public boolean isEmpty() {
return first == null;
}
public int size() {
return N;
}
public void push(Item data) {
Node oldFirst = first;//儲存原來的棧頂結點
first = new Node();//構建新的棧頂結點
first.data = data;//壓棧
first.next = oldFirst;//棧頂結點指向舊棧頂結點
N++;//棧的大小變大
}
public Item pop() {
Item data = first.data;//彈棧
first = first.next;//棧頂變化
N--;//棧的大小縮小
return data;
}
}