【算法】實現棧和隊列
阿新 • • 發佈:2018-02-25
deque 錯誤 line b- 判斷 linked 元素 type file
棧(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)原則的集合類型,可以將其類比為生活中一些以公平性為原則的服務場景: 排成一排的客戶等待服務,等待最久即最先入列的客戶應該最先提供服務(出列)實現隊列也有兩種方式,一種是鏈表, 另一種是循環數組 隊列和棧在實現上的不同
- 棧遵循後進先出的原則,所以要在數組或鏈表同一端做添加和刪除操作
- 隊列遵循先進先出的原則, 所以要在數組或鏈表的兩端分別做插入和刪除的操作
通過鏈表實現隊列
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個元素位置去存儲隊列元素 【完】
【算法】實現棧和隊列