【算法】實現棧和隊列

棧(stack)

棧(stack)是一種後進先出(LIFO)的集合類型, 即後來添加的數據會先被刪除
 

 

能夠將其類比於下面文件的取放操做:新到的文件會被先取走,這使得每次取走的文件都是最新的。

 

 
棧能夠用數組或者隊列去實現
下面要實現的棧的API以下圖所示:
 

 

 

用數組實現棧

下面咱們經過數組實現一個指定了初始容量,但隨着元素的增長可以動態地擴張容量的棧。注意: 由於數組指定大小後不可改變, 因此咱們要定義自動擴大棧容量的操做
public class ArrayStack<Item> {
  // 棧元素的總數
  private int N = 0;
  // 存放棧元素的數組
  private Item [] items;
  public ArrayStack (int M) {
    items = (Item[]) new Object[M];
  }
  /**
   * @description: 調整棧的大小
   */
  private void resize (int max) {
    Item [] temp = (Item [])new Object[max];
    for (int i =0;i<items.length;i++) {
      temp[i] = items[i];
    }
    items = temp;
  }
  /**
   * @description: 向棧頂插入元素
   */
  public void push (Item item) {
    // 當棧滿了的時候, 將棧的數組大小擴大爲原來兩倍
    if (N==items.length) resize(2*N);
    items[N++] = item;
  }
  /**
   * @description: 從棧頂刪除元素,並將刪除的元素返回
   */
  public Item pop () {
    // 當棧仍是空的時候, 不刪除而且返回空
    if(isEmpty()) return null;
    // 保存將要被刪除的元素
    Item i = items[N-1];
    // 將該元素刪除
    items[N-1] = null;
    // 棧的長度減1
    N--;
    return i;
  }
 
  /**
   * @description: 判斷棧是否爲空
   */
  public boolean isEmpty () {
    return N == 0;
  }
  /**
   * @description: 返回棧的大小
   */
  public int size () {
    return N;
  }
 
  public static void main (String args []) {
    // 開始時指定棧的容量爲2
    ArrayStack<Integer> stack = new ArrayStack<>(2);
    // 向棧頂依次添加3個元素
    stack.push(1);
    stack.push(2);
    stack.push(3);
    // 添加3後棧的容量自動擴大了
    // 依次從棧頂刪除3個元素
    System.out.println(stack.pop());
    System.out.println(stack.pop());
    System.out.println(stack.pop());
  }
}

 

輸出:
3
2
1

 

用鏈表實現棧

下面展現用鏈表實現的棧的代碼, 注意: 添加和刪除操做都是在鏈表的頭部進行的
public class LinkedListStack<Item> {
  // 棧中元素的總數
  private int N = 0;
  // 鏈表頭元素
  private Node front;
  // 內部結點類
  private class Node {
    Item item;
    Node next;
  }
  /**
   * @description: 向棧頂插入元素
   */
  public void push (Item item) {
    Node oldFront = front;
    // 向鏈表頭部插入新的結點
    front = new Node();
    front.item = item;
    // 將新頭結點的next指針指向舊的頭結點
    front.next = oldFront;
    // 棧的長度加1
    N++;
  }
  /**
   * @description: 向棧頂刪除元素,並將刪除的元素返回
   */
  public Item pop () {
    // 當棧仍是空的時候, 不刪除而且返回空
    if(isEmpty()) return null;
    // 保存待刪除的項以便返回
    Item item = front.item;
    // 刪除原頭結點
    front = front.next;
    // 棧的長度減1
    N--;
    return item;
  }
  /**
   * @description: 判斷棧是否爲空
   */
  public boolean isEmpty () {
    return N == 0;
  }
  /**
   * @description: 返回棧的大小
   */
  public int size () {
    return N;
  }
 
  public static void main (String args []) {
    // 建立棧
    LinkedListStack<Integer> stack = new LinkedListStack<>();
    // 向棧頂依次添加3個元素
    stack.push(1);
    stack.push(2);
    stack.push(3);
    // 依次從棧頂刪除3個元素
    System.out.println(stack.pop());
    System.out.println(stack.pop());
    System.out.println(stack.pop());
  }
}

 

輸出:
3
2
1

 

隊列(queue)

隊列屬於一種遵循先進先出(FIFO)原則的集合類型,能夠將其類比爲生活中一些以公平性爲原則的服務場景: 排成一排的客戶等待服務,等待最久即最早入列的客戶應該最早提供服務(出列)

 

 
 
實現隊列也有兩種方式,一種是鏈表, 另外一種是循環數組
隊列和棧在實現上的不一樣
  • 棧遵循後進先出的原則,因此要在數組或鏈表同一端作添加和刪除操做
  • 隊列遵循先進先出的原則, 因此要在數組或鏈表的兩端分別作插入和刪除的操做
 
咱們要實現的隊列API以下圖所示:

 

 

經過鏈表實現隊列

public class LinkedListQueue<Item> {
  // 鏈表中的結點數目
  private int N = 0;
  // 鏈表頭結點
  private Node front = null;
  // 鏈表尾結點
  private Node rear = null;
  // 結點內部類
  private class Node {
    Item item;
    Node next;
  }
  /**
   * @description: 元素入列(在鏈表尾部添加)
   */
  public void enqueue (Item item) {
    Node oldRear = rear;
    rear = new Node();
    rear.item = item;
    if (isEmpty()) front = rear;
    else           oldRear.next = rear;
    N++;
  }
  /**
   * @description: 元素出列(在鏈表頭部刪除)
   */
  public Item dequeue () {
    if(isEmpty()) return null;
    Item item = front.item;
    front = front.next;
    N--;
    if(isEmpty()) rear = null;
    return item;
  }
  /**
   * @description: 判斷隊列是否爲空
   */
  public boolean isEmpty () {
    return N == 0;
  }
  /**
   * @description: 返回隊列長度
   */
  public int size () {
    return N;
  }
 
  public static void main (String args []) {
    LinkedListQueue<String> queue = new LinkedListQueue<>();
    queue.enqueue("A");
    queue.enqueue("B");
    queue.enqueue("C");
    queue.enqueue("D");
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
  }
}

 

輸出:
A
B
C
D

 

頭部刪除-尾部添加 OR 頭部添加-尾部刪除?
在上面的代碼中,咱們是經過在鏈表尾部添加結點,在鏈表頭部刪除結點的操做實現隊列, 那能不能經過在鏈表頭部添加結點,在鏈表尾部刪除結點的方式實現隊列呢? 這是能夠的,但並非一個合適的作法,由於若是這樣操做,在單向鏈表的條件下,須要將鏈表從頭至尾迭代一遍才能實現刪除操做,而咱們經過上面的「頭部刪除-尾部添加」就能避免這種開銷。
 
經過在鏈表頭部添加結點,在鏈表尾部刪除結點實現隊列(不推薦)
  /**
   * @description: 元素入列(在鏈表頭部添加)
   */
  public void enqueue (Item item) {
    Node oldFront = front;
    front = new Node();
    front.item = item;
    front.next = oldFront;
    if (isEmpty()) rear = front;
    N++;
  }
  /**
   * @description: 元素出列(在鏈表尾部刪除)
   */
  public Item dequeue () {
    if (isEmpty()) return null;
    if (size()==1) {
      Item item = rear.item;
      front = null;
      rear = null;
      N--;
      return item;
    }
    Node x = front;
    while (!x.next.equals(rear)) {
      x=x.next;
    }
    Item item = x.next.item;
    x.next = null;
    rear = x;
    N--;
    return item;
  }

 

 

經過循環數組實現隊列

除了鏈表以外, 另一種實現隊列的方式是循環數組。
爲何須要循環數組?
由於僅靠普通的數組實現隊列可能會致使一個問題: 數組大量空位元素得不到利用。
例以下圖所示, 在數組的實現方式中,咱們會使用front和rear兩個指針跟蹤隊列頭部元素和尾部元素的位置,在動態的出列和入列操做中它們的位置會不斷髮生變化,隨着出列操做fron指針t會不斷後移(a->b->c->d), 當front和rear到達圖d的狀態時,咱們發現:front前面的元素有一大段由於出列而騰出的空的元素沒有獲得利用,而此時又沒法繼續入列了(rear指針到達數組尾部,再次入列將致使數組越界的錯誤)

 

如今咱們有一個方式能夠解決這個問題: 將數組的頭部和尾部連在一塊兒,構成一個循環數組:

 

 
代碼以下圖所示, 能夠看到,實現循環的關鍵是使用的一個取餘數的操做,使得指針在移動到數組尾部的時候,可以從新移動到數組的頭部:
 
public class CircleArrayQueue<Item> {
  // 隊列元素總數
  private int N = 0;
  // 數組長度
  private int M;
  // 隊列頭部元素指針
  private int front = 0;
  // 隊列尾部元素指針
  private int rear = 0;
  private Item [] items;
  public CircleArrayQueue (int M) {
    this.M = M;
    items = (Item [])new Object[M];
  }
  /**
   * @description: 入列操做
   */
  public void enqueue (Item item) {
    // 當隊列爲空時, 不能進行入列操做
    if (isFull()) return;
    // 向隊列尾部插入元素
    items[rear] = item;
    // 用數組長度M取餘, 使得rear到達數組尾部時能返回數組頭部
    rear = (rear + 1) % M;
    // 增長隊列長度
    N++;
  }
  /**
   * @description: 出列,並返回被刪除項
   */
  public Item dequeue () {
    // 當隊列爲滿時, 不能進行出列操做
    if (isEmpty()) return null;
    // 保存待刪除元素, 以待返回
    Item item = items[front];
    // 刪除隊列頭部元素
    items[front] = null;
    // 用數組長度M取餘, 使得front到達數組尾部時能返回數組頭部
    front = (front + 1) % M;
    // 減小隊列長度
    N--;
    // 返回刪除元素
    return item;
  }
  /**
   * @description: 判斷隊列是否滿了
   */
  public boolean isFull () {
    return N == M;
  }
  /**
   * @description: 判斷隊列是否爲空
   */
  public boolean isEmpty () {
    return N == 0;
  }
  /**
   * @description: 返回隊列元素總數
   */
  public int size () {
    return N;
  }

  public static void main (String args []) {
    CircleArrayQueue<Integer> queue = new CircleArrayQueue<>(3);
    // 依次入列三個元素
    queue.enqueue(1);
    queue.enqueue(2);
    queue.enqueue(3);
    // 依次出列三個元素
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
  }
}

 

 

輸出:
1
2
3

 

判斷循環數組的滿狀態和空狀態

在循環數組的實現中,一個很是重要的操做就是區分數組是處在"滿"狀態仍是「空」狀態,由於當front和rear指向同一個元素位置時,既可能處在滿狀態也可能處在空狀態。 上面的代碼裏咱們是經過一個表示隊列元素總數的變量N去判斷的,除此以外,咱們也能夠經過另一種不依賴於變量N的方式去判斷數組的滿和空的狀態, 但代價是少用一個元素空間,例如:
(下面的代碼除了isEmpty和isFull外都和上面相同)
public class CircleArrayQueue2<Item> {
  private int M;
  private int front = 0;
  private int rear = 0;
  private Item [] items;
  public CircleArrayQueue2 (int M) {
    this.M = M;
    items = (Item [])new Object[M];
  }
 
  public void enqueue (Item item) {
    if (isFull()) return;
    items[rear] = item;
    rear = (rear + 1) % M;
  }
 
  public Item dequeue () {
    if (isEmpty()) return null;
    Item item = items[front];
    items[front] = null;
    front = (front + 1) % M;
    return item;
  }
 
  public boolean isFull () {
    return (rear + 1) % M == front;
  }
 
  public boolean isEmpty () {
    return rear == front;
  }
 
  public static void main (String args []) {
    CircleArrayQueue2<Integer> queue = new CircleArrayQueue2<>(3);
    queue.enqueue(1);
    queue.enqueue(2);
    queue.enqueue(3);
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
  }
}

 

輸出:
1
2
null

 

由輸出可看出, 在數組長度爲3時, 咱們實際上只能有2個元素位置去存儲隊列元素
 
【完】
相關文章
相關標籤/搜索