咱們今天主要來談談「棧」以及隊列這兩種數據結構。算法
回顧一下上一章中【數據結構01】數組中,在數組中只要知道數據的下標,即可經過順序搜索很快查詢到數據,能夠根據下標不一樣自由查找,然而今天要講的「棧」以及隊列這兩種數據結構訪問是受限制的,只容許在一端讀取、插入和刪除數據,這時候對它存在的意義產生了很大的疑惑。由於會以爲,相比數組和鏈表,棧帶給個人只有限制,並無任何優點。那我直接使用數組或者鏈表不就行了嗎?爲何還要用這個「操做受限」的「棧」呢?事實上,從功能上來講,數組或鏈表確實能夠替代棧,但你要知道,特定的數據結構是對特定場景的抽象,並且,數組或鏈表暴露了太多的操做接口,操做上的確靈活自由,但使用時就比較不可控,天然也就更容易出錯。數組
@緩存
首先,如何理解「棧」?用現實一個通俗貼切的例子,咱們平時放盤子的時候,都是從下往上一個一個放;取的時候,咱們是從上往下一個一個地依次取,不能從中間任意抽出,先進後出,這就是典型的「棧」結構,當某個數據集合只涉及在一端插入和刪除數據,而且知足後進先出、先進後出的特性,咱們就應該首選「棧」這種數據結構。數據結構
其次如何理解隊列?一樣用現實一個通俗貼切的例子,平時在校的時候飯堂吃飯都是排隊,並且不能插隊,先進先出,這就是典型的隊列結構併發
實際上,棧既能夠用數組來實現,也能夠用鏈表來實現。用數組實現的棧,咱們叫做順序棧,用鏈表實現的棧,咱們叫做鏈式棧。不論是順序棧仍是鏈式棧,咱們存儲數據只須要一個大小爲n的數組就夠了。在入棧和出棧過程當中,只須要一兩個臨時變量存儲空間,因此空間複雜度是O(1)。框架
注意,這裏存儲數據須要一個大小爲n的數組,並非說空間複雜度就是O(n)。由於,這n個空間是必須的,沒法省掉。因此咱們說空間複雜度的時候,是指除了本來的數據存儲空間外,算法運行還須要額外的存儲空間。性能
空間複雜度分析是否是很簡單?時間複雜度也不難。不論是順序棧仍是鏈式棧,入棧、出棧只涉及棧頂個別數據的操做,因此時間複雜度都是O(1)。測試
還有一點,JVM內存管理中有個「堆棧」的概念。棧內存用來存儲局部變量和方法調用,堆內存用來存儲Java中的對象。那JVM裏面的「棧」跟咱們這裏說的「棧」是否是一回事呢?若是不是,那它爲何又叫做「棧」呢?知道的大牛請自覺評論區見面~this
public class MyStack { //棧的底層咱們使用數組來存儲數據 int[] elements; public MyStack() { elements = new int[0]; } //壓入元素 public void push(int element) { // 建立一個新的數組 int[] newArr = new int[elements.length + 1]; // 把原數組中的元素複製到新數組中 for (int i = 0; i < elements.length; i++) { newArr[i] = elements[i]; } // 把添加的元素放入新數組中 newArr[elements.length] = element; // 使用新數組替換舊數組 elements = newArr; } //取出棧頂元素 public int pop() { //棧中沒有元素 if(elements.length==0) { throw new RuntimeException("stack is empty"); } //取出數組的最後一個元素 int element = elements[elements.length-1]; //建立一個新的數組 int[] newArr = new int[elements.length-1]; //原數組中除了最後一個元素的其它元素都放入新的數組中 for(int i=0;i<elements.length-1;i++) { newArr[i]=elements[i]; } //替換數組 elements=newArr; //返回棧頂元素 return element; } //查看棧頂元素 public int peek() { //棧中沒有元素 if(elements.length==0) { throw new RuntimeException("stack is empty"); } return elements[elements.length-1]; } //判斷棧是否爲空 public boolean isEmpty() { return elements.length==0; } }
import demo2.MyStack; public class TestMyStack { public static void main(String[] args) { //建立一個棧 MyStack ms = new MyStack(); //壓入數組 ms.push(9); ms.push(8); ms.push(7); //最出棧頂元素 System.out.println(ms.pop()); System.out.println(ms.pop()); System.out.println(ms.pop()); //查看棧頂元素 // System.out.println(ms.peek()); System.out.println(ms.isEmpty()); } }
package stack; /** * 基於鏈表實現的棧。 */ public class StackBasedOnLinkedList { private Node top = null; public void push(int value) { Node newNode = new Node(value, null); // 判斷是否棧空 if (top == null) { top = newNode; } else { newNode.next = top; top = newNode; } } /** * 我用-1表示棧中沒有數據。 */ public int pop() { if (top == null) return -1; int value = top.data; top = top.next; return value; } public void printAll() { Node p = top; while (p != null) { System.out.print(p.data + " "); p = p.next; } System.out.println(); } private static class Node { private int data; private Node next; public Node(int data, Node next) { this.data = data; this.next = next; } public int getData() { return data; } } }
棧只支持兩個基本操做:入棧push()和出棧pop()。隊列跟棧很是類似,支持的操做也頗有限,最基本的操做也是兩個:入隊enqueue(),放一個數據到隊列尾部;出隊dequeue(),從隊列頭部取一個元素。因此,隊列跟棧同樣,也是一種操做受限的線性表數據結構。隊列的概念很好理解,基本操做也很容易掌握。做爲一種很是基礎的數據結構,隊列的應用也很是普遍,特別是一些具備某些額外特性的隊列,好比循環隊列、阻塞隊列、併發隊列。它們在不少偏底層系統、框架、中間件的開發中,起着關鍵性的做用。好比高性能隊列Disruptor、Linux環形緩存,都用到了循環併發隊列;Java concurrent併發包利用ArrayBlockingQueue來實現公平鎖等。
跟棧同樣,隊列能夠用數組來實現,也能夠用鏈表來實現。用數組實現的棧叫做順序棧,用鏈表實現的棧叫做鏈式棧。一樣,用數組實現的隊列叫做順序隊列,用鏈表實現的隊列叫做鏈式隊列
package queue; // 用數組實現的隊列 public class ArrayQueue { // 數組:items,數組大小:n private String[] items; private int n = 0; // head表示隊頭下標,tail表示隊尾下標 private int head = 0; private int tail = 0; // 申請一個大小爲capacity的數組 public ArrayQueue(int capacity) { items = new String[capacity]; n = capacity; } // 入隊 public boolean enqueue(String item) { // 若是tail == n 表示隊列已經滿了 if (tail == n) return false; items[tail] = item; ++tail; return true; } // 出隊 public String dequeue() { // 若是head == tail 表示隊列爲空 if (head == tail) return null; // 爲了讓其餘語言的同窗看的更加明確,把--操做放到單獨一行來寫了 String ret = items[head]; ++head; return ret; } public void printAll() { for (int i = head; i < tail; ++i) { System.out.print(items[i] + " "); } System.out.println(); } }
package queue; /** * 基於鏈表實現的隊列 */ public class QueueBasedOnLinkedList { // 隊列的隊首和隊尾 private Node head = null; private Node tail = null; // 入隊 public void enqueue(String value) { if (tail == null) { Node newNode = new Node(value, null); head = newNode; tail = newNode; } else { tail.next = new Node(value, null); tail = tail.next; } } // 出隊 public String dequeue() { if (head == null) return null; String value = head.data; head = head.next; if (head == null) { tail = null; } return value; } public void printAll() { Node p = head; while (p != null) { System.out.print(p.data + " "); p = p.next; } System.out.println(); } private static class Node { private String data; private Node next; public Node(String data, Node next) { this.data = data; this.next = next; } public String getData() { return data; } } }
用數組來實現隊列的時候會有數據搬移操做,這樣入隊操做性能就會受到影響。那有沒有辦法可以避免數據搬移呢?咱們來看看循環隊列的解決思路。
循環隊列,顧名思義,它長得像一個環。本來數組是有頭有尾的,是一條直線。如今咱們把首尾相連,扳成了一個環。
package queue; public class CircularQueue { // 數組:items,數組大小:n private String[] items; private int n = 0; // head表示隊頭下標,tail表示隊尾下標 private int head = 0; private int tail = 0; // 申請一個大小爲capacity的數組 public CircularQueue(int capacity) { items = new String[capacity]; n = capacity; } // 入隊 public boolean enqueue(String item) { // 隊列滿了 if ((tail + 1) % n == head) return false; items[tail] = item; tail = (tail + 1) % n; return true; } // 出隊 public String dequeue() { // 若是head == tail 表示隊列爲空 if (head == tail) return null; String ret = items[head]; head = (head + 1) % n; return ret; } public void printAll() { if (0 == n) return; for (int i = head; i % n != tail; ++i) { System.out.print(items[i] + " "); } System.out.println(); } }
好了,到這裏,總結一下隊列,隊列最大的特色就是先進先出,主要的兩個操做是入隊和出隊。跟棧同樣,它既能夠用數組來實現,也能夠用鏈表來實現。用數組實現的叫順序隊列,用鏈表實現的叫鏈式隊列。特別是長得像一個環的循環隊列。在數組實現隊列的時候,會有數據搬移操做,要想解決數據搬移的問題,咱們就須要像環同樣的循環隊列。
若是本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝~
歡迎各位關注個人公衆號,一塊兒探討技術,嚮往技術,追求技術...說好了來了就是盆友喔...