1. 棧(Stack)
1.1 概念
棧:一種特殊的線性表,其只允許在固定的一端進行插入和刪除元素操作。進行數據插入和刪除操作的一端稱為棧 頂,另一端稱為棧底。棧中的數據元素遵守后進先出LIFO(Last In First Out)的原則。
壓棧:棧的插入操作叫做進棧/壓棧/入棧,入數據在棧頂。
出棧:棧的刪除操作叫做出棧。出數據在棧頂。
1.2 棧的使用
從上圖中可以看到,Stack繼承了Vector,Vector和ArrayList類似,都是動態的順序表,不同的Vector是線程安全;Vector類,是線程安全的動態數組,但是性能較差 , 現在已經不是很常用了 , 可以說已經過時了。
常用方法
方法 | 功能 |
Stack() | 構造一個空的棧 |
E push(E e) | 將e入棧,并返回e |
E pop() | 將棧頂元素出棧并返回 |
E peek() | 獲取棧頂元素 |
int size() | 獲取棧中有效元素個數 |
boolean empty() | 檢測棧是否為空 |
public static void main(String[] args) {
Stack<Integer> s = new Stack();
s.push(1);
s.push(2);
s.push(3);
s.push(4);
System.out.println(s.size()); // 獲取棧中有效元素個數---> 4
System.out.println(s.peek()); // 獲取棧頂元素---> 4
s.pop(); // 4出棧,棧中剩余1 2 3,棧頂元素為3
System.out.println(s.pop()); // 3出棧,棧中剩余1 2 棧頂元素為3
if(s.empty()){
System.out.println("棧空");
}else{
System.out.println(s.size());}
}
1.3 棧的模擬實現
從上圖中可以看到,Stack繼承了Vector,Vector和ArrayList類似,都是動態的順序表,不同的是Vector是線程安 全的。
代碼實現
1. 構造方法
class MyStack{private int[] arr;// size 記錄棧中元素個數private int size;public MyStack(){// 調用無參構造方法 默認最大容量12this(12);}public MyStack(int MaxSize){this.arr = new int[MaxSize];}
}
2. 入棧(push)
// 入棧public int push(int value){if(this.size == arr.length){// 棧滿 ,需要擴容int[] copyArr;// 復制arr 數組并擴容一倍copyArr = Arrays.copyOf(arr,2 * arr.length);arr = copyArr;}//將元素添加到size位置this.arr[size] = value;// 元素個數加一this.size++;// 返回添加元素return value;}
3. 出棧(pop)
// 出棧public int pop(){if(this.size == 0){//沒有元素//拋出運行時異常,此處也可以自定義異常throw new RuntimeException("棧中沒有元素,不能出棧....");}// 獲得棧頂元素int value = this.arr[size - 1];// size - 1 之后, 下一次插入時會覆蓋原數據,利用覆蓋替代刪除this.size--;return value;}
4.獲取棧頂元素(peek)
// 獲取棧頂元素public int peek(){if(this.size == 0){//沒有元素//拋出運行時異常,此處也可以自定義異常throw new RuntimeException("棧中沒有元素,不能出棧....");}return this.arr[this.size - 1];}
5.獲取元素個數(getSize)
//獲取元素個數public int getSize(){return this.size;}
6.判斷棧是否為空(isEmpty)
//判斷元素是否為空public boolean isEmpty(){return this.size == 0;}
完整代碼
import java.util.Arrays;public class MyStack{private int[] arr;// size 記錄棧中元素個數private int size;public MyStack(){// 調用無參構造方法 默認最大容量12this(12);}public MyStack(int MaxSize){this.arr = new int[MaxSize];}// 入棧public int push(int value){if(this.size == arr.length){// 棧滿 ,需要擴容int[] copyArr;// 復制arr 數組并擴容一倍copyArr = Arrays.copyOf(arr,2 * arr.length);arr = copyArr;}//將元素添加到size位置this.arr[size] = value;// 元素個數加一this.size++;// 返回添加元素return value;}// 出棧public int pop(){if(isEmpty()){//沒有元素//拋出運行時異常,此處也可以自定義異常throw new RuntimeException("棧中沒有元素,不能出棧....");}// 獲得棧頂元素int value = this.arr[size - 1];// size - 1 之后, 下一次插入時會覆蓋原數據,利用覆蓋替代刪除this.size--;return value;}// 獲取棧頂元素public int peek(){if(isEmpty()){//沒有元素//拋出運行時異常,此處也可以自定義異常throw new RuntimeException("棧中沒有元素,不能出棧....");}return this.arr[this.size - 1];}//獲取元素個數public int getSize(){return this.size;}//判斷元素是否為空public boolean isEmpty(){return this.size == 0;}
}
1.4 棧的應用場景
1. 改變元素的序列
1. 若進棧序列為 1,2,3,4 ,進棧過程中可以出棧,則下列不可能的一個出棧序列是( )
? ? ?A: 1,4,3,2? ? ? ? ? ? ? ? ? ? ? B: 2,3,4,1? ? ? ? ? ? ? ? ?C: 3,1,4,2? ?? ? ? ? ? ? ? ? ? ? ? ? ?D: 3,4,2,1
根據棧先進后出的性質,結合題目中進棧的過程中也可以出棧,如A選項:1進1出,2進3進4進,4出3出2出即符合題意,同理C選項,1進2進3進3出之后不可能直接出1,故C選項不可能實現。
2.一個棧的初始狀態為空。現將元素1、2、3、4、5、A、B、C、D、E依次入棧,然后再依次出棧,則元素出棧的順 序是( )。
A: 12345ABCDE? ? ? ? ? B: EDCBA54321? ? ? ? ?C: ABCDE12345? ? ? ? ? ?D: 54321EDCBA
先進后出,依次入棧,依次出棧,故B選項合理
2. 將遞歸轉化為循環
遞歸實現逆序打印
public void display(ListNode head){if(head == null){return;}//直到鏈表末尾,再歸回去if(head.next == null){System.out.println(head.val+" ");return;}display(head.next);System.out.println(head.val+" ");
}
使用棧實現逆序打印
public void display(ListNode head){if(head == null){return;}Stack<ListNode> stack = new Stack<>();ListNode cur = head;while(cur!= null){stack.push(cur);cur = cur.next;}while(!stack.empty()){ListNode ret = stack.pop();System.out.println(ret.val+" ");}}
2. 隊列(Queue)
2.1 概念
隊列:只允許在一端進行插入數據操作,在另一端進行刪除數據操作的特殊線性表,隊列具有先進先出FIFO(First In First Out)
入隊列:進行插入操作的一端稱為隊尾(Tail/Rear)
出隊列:進行刪除操作的一端稱為隊頭 (Head/Front)
2.2 隊列的使用
在Java中,Queue是個接口,底層是通過鏈表實現的。
方法 | 功能 |
boolean offer(E e)? | 入隊列 |
E poll()? | 出隊列 |
peek()? | 獲取隊頭元素 |
int size()? | 獲取隊列中有效元素個數 |
boolean isEmpty() | 檢測隊列是否為空 |
注意:Queue是個接口,在實例化時必須實例化LinkedList的對象,因為LinkedList實現了Queue接口。
public static void main(String[] args) {
Queue<Integer> q = new LinkedList<>();
q.offer(1);
q.offer(2);
q.offer(3);
q.offer(4);
q.offer(5); // 從隊尾入隊列
System.out.println(q.size());
System.out.println(q.peek()); // 獲取隊頭元素
q.poll();
System.out.println(q.poll()); // 從隊頭出隊列,并將刪除的元素返回
if(q.isEmpty()){
System.out.println("隊列空");
}else{
System.out.println(q.size());
}
}
2.3 隊列模擬實現
隊列中既然可以存儲元素,那底層肯定要有能夠保存元素的空間,通過前面線性表的學習了解到常見的空間類型有兩種:順序結構和鏈式結構?。
public class Queue {// 雙向鏈表節點public static class ListNode{ListNode next;ListNode prev;int value;ListNode(int value){this.value = value;}}ListNode first; // 隊頭ListNode last; // 隊尾int size = 0;// 入隊列---向雙向鏈表位置插入新節點public void offer(int e){ListNode newNode = new ListNode(e);if(first == null){first = newNode;
// last = newNode;}else{last.next = newNode;newNode.prev = last;
// last = newNode;}last = newNode;size++;}// 出隊列---將雙向鏈表第一個節點刪除掉public int poll(){
// 1. 隊列為空
// 2. 隊列中只有一個元素----鏈表中只有一個節點---直接刪除
// 3. 隊列中有多個元素---鏈表中有多個節點----將第一個節點刪除int value = 0;if(first == null){return null;}else if(first == last){last = null;first = null;}else{value = first.value;first = first.next;first.prev.next = null;first.prev = null;}--size;return value;}// 獲取隊頭元素---獲取鏈public int peek(){if(first == null){return null;}return first.value;}public int size() {return size;}public boolean isEmpty(){return first == null;}
}
2.4 循環隊列
實際中我們有時還會使用一種隊列叫循環隊列。如操作系統課程講解生產者消費者模型時可以就會使用循環隊列。環形隊列通常使用數組實現。
數組下標循環的小技巧?
1. 下標最后再往后(offset?小于 array.length): index = (index + offset) % array.length
2. 下標最前再往前(offset 小于 array.length): index = (index + array.length - offset)%array.length
如何區分空與滿
1. 通過添加 size 屬性記錄
2. 保留一個位置
3. 使用標記
public class CircularQueue {private int front;private int rear;private int[] circle;public CircularQueue(int k) {//浪費掉一個存儲空間circle = new int[k+1];}//入隊列public boolean enQueue(int value) {if (isFull()) {return false;}circle[rear] = value;//因為是循環隊列,不能寫++,要以取模的方式rear = (rear + 1) % circle.length;return true;}//出隊列public boolean deQueue() {if (isEmpty()) {return false;}front = (front + 1) % circle.length;return true;}//返回隊頭元素public int Front() {if (isEmpty()) {return -1;}return circle[front];}//返回隊尾元素public int Rear() {if (isEmpty()) {return -1;}return circle[(rear - 1 + circle.length) % circle.length];}public boolean isEmpty() {return rear == front;}public boolean isFull() {return ((rear + 1) % circle.length) == front;}}
3.? 雙端隊列 (Deque)
雙端隊列(deque)是指允許兩端都可以進行入隊和出隊操作的隊列,deque 是 “double ended queue” 的簡稱。那就說明元素可以從隊頭出隊和入隊,也可以從隊尾出隊和入隊。
Deque是一個接口,使用時必須創建LinkedList的對象。
在實際工程中,使用Deque接口是比較多的,棧和隊列均可以使用該接口。
Deque<Integer> stack = new ArrayDeque<>();//雙端隊列的線性實現
Deque<Integer> queue = new LinkedList<>();//雙端隊列的鏈式實現
4. 棧和隊列的互相實現
用棧實現隊列:
class MyQueue {public Stack<Integer> stack1;public Stack<Integer> stack2;public MyQueue() {stack1 = new Stack<>();stack2 = new Stack<>();}public void push(int x) {stack1.push(x);}public int pop() {if (stack2.isEmpty()) {
in2out();}return stack2.pop();}public int peek() {if (stack2.isEmpty()){
in2out();}return stack2.peek();}public boolean empty() {return stack1.isEmpty() && stack2.isEmpty();}private void in2out() {while (!stack1.isEmpty()) {stack2.push(stack1.pop());}}
}
用隊列實現棧:
class MyStack {Queue<Integer> queue1;Queue<Integer> queue2;public MyStack() {queue1 = new LinkedList<Integer>();queue2 = new LinkedList<Integer>();}public void push(int x) {queue2.offer(x);while (!queue1.isEmpty()) {queue2.offer(queue1.poll());}Queue<Integer> temp = queue1;queue1 = queue2;queue2 = temp;}public int pop() {return queue1.poll();}public int top() {return queue1.peek();}public boolean empty() {return queue1.isEmpty();}
}