1. 程式人生 > >棧(Java)

棧(Java)

前言:博主第一次寫部落格,用詞或一些概念上可能會出現一些錯誤,希望大家指出來。

下壓棧(LIFO)

下壓棧(簡稱棧)是一種具有後進先出特點的集合了型別。 棧的應用性很廣泛,使用棧可以解決括號匹配問題,迷宮問題,表示式求值這類問題。

我們先用圖形來解釋一下什麼是“棧”吧。 首先我們假設這裡有一個空箱子(空棧),我們手上有3本書,Java,C++,以及PHP。 空棧以及3個元素

如果我們想將一本java書放入,需要怎麼做呢? 其實非常簡單,直接放進入就好了。(壓棧,也叫入棧) 放入Java的棧 當我們依次再將C++、PHP的書放進去,就會形成下面的狀態。 放入3個元素的棧 我們已經成功的將三本書放入,接下來我們需要考慮如何取出目標書籍。

比如:當我們需要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指向它。 5入棧

此時再進入一個2,那麼棧頂元素更新,變為2,First也就重新指向了2。 2入棧

此時再存入一個3,操作與上面的方法一致。 3入棧

彈棧時,則只需將頭結點彈出,First指向2(棧頂指向新的結點),此時就恢復到了第二步。 3彈棧

也就是說,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;
 }
}