寫在前面: 博主是一名軟件工程系大數據應用開發專業大二的學生,暱稱來源於《愛麗絲夢遊仙境》中的Alice和自己的暱稱。作爲一名互聯網小白,
寫博客一方面是爲了記錄自己的學習歷程,一方面是希望能夠幫助到很多和自己一樣處於起步階段的萌新
。由於水平有限,博客中難免會有一些錯誤,有紕漏之處懇請各位大佬不吝賜教!個人小站:http://alices.ibilibili.xyz/ , 博客主頁:https://alice.blog.csdn.net/
儘管當前水平可能不及各位大佬,但我還是希望自己能夠做得更好,因爲一天的生活就是一生的縮影
。我希望在最美的年華,做最好的自己
!
之前已經寫過關於數組和鏈表的博客,按照學習的順序,本篇我們來學習點關於棧和隊列的知識。
棧
棧(stack)是一種線性數據結構,它就像一個上圖所示的放入乒乓球的圓筒容器, 棧中的元素只能先入後出(First In Last Out,簡稱FILO)。最早進入的元素存放的位置 叫作棧底(bottom),最後進入的元素存放的位置叫作棧頂(top)。
棧這種數據結構既可以用數組來實現,也可以用鏈表來實現。
棧的數組實現如下
棧的鏈表實現如下
棧的基本操作
棧常用的操作包含入棧和出棧。
入棧
入棧操作(push)就是把新元素放入棧中,只允許從棧頂一側放入元素,新元素的位置將會成爲新的棧頂。
這裏我們以數組實現爲例。
出棧
出棧操作(pop)就是把元素從棧中彈出,只有棧頂元素才允許出棧,出棧元素的前一個元素將會成爲新的棧頂。
這裏我們以數組實現爲例。
用數組實現簡單棧
/**
* @Author: Alice菌
* @Date: 2020/6/20 20:24
* @Description:
* 用數組實現簡單棧
*/
public class OwnArrayStack<T> {
private static final Integer DEFAULT_SIZE = 10;
/*** 棧 */
private T[] items;
/*** 棧頂所在角標 */
private int top = -1;
/*** 棧總容量 */
private int size;
public OwnArrayStack() {
this(DEFAULT_SIZE);
}
public OwnArrayStack(int len) {
if (len <= 0) {
throw new IndexOutOfBoundsException("new instance error, len need more than 0, size: " + len);
}
size = len;
items = (T[]) new Object[size];
}
/**
* 判斷棧是否爲控
*
* @return
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 進棧
*
* @param t
* @return
*/
public boolean push(T t) {
if (top == size - 1) {
return false;
}
items[++top] = t;
return true;
}
/**
* 出棧
*
* @return
*/
public T pop() {
if (isEmpty()) {
return null;
}
T item = items[top];
items[top--] = null;
return item;
}
/**
* 獲取棧頂元素
*
* @return
*/
public T top() {
if (isEmpty()) {
return null;
}
return items[top];
}
public static void main(String[] args) {
OwnArrayStack<Integer> stack = new OwnArrayStack<Integer>();
for (int i = 0; i < DEFAULT_SIZE + 1; i++) {
System.out.println("push: " + stack.push(i) + ", item: " + i);
}
for (int i = 0; i < DEFAULT_SIZE + 1; i++) {
System.out.println("top: " + stack.top() + ", pop: " + stack.pop());
}
}
}
用鏈表實現簡單棧
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @Author: Alice菌
* @Date: 2020/6/20 20:36
* @Description:
*/
public class OwnLinkedStack<T> {
private static final Integer DEFAULT_SIZE = 10;
/*** 棧頂元素 */
private Node top;
/*** 棧當前容量 */
private Integer index;
/*** 棧總容量 */
private Integer size;
public OwnLinkedStack() {
this(DEFAULT_SIZE);
}
public OwnLinkedStack(Integer len) {
if (len <= 0) {
throw new IndexOutOfBoundsException("new instance error, len need more than 0, size: " + len);
}
index = -1;
size = len;
}
/**
* 判斷棧是否爲空
*
* @return
*/
public Boolean isEmpty() {
return top == null;
}
/**
* 壓棧
*
* @param t
* @return
*/
public boolean push(T t) {
if (index >= size - 1) {
return false;
}
Node old = top;
top = new Node(t, old);
index++;
return true;
}
/**
* 彈棧
*
* @return
*/
public Node pop() {
if (isEmpty()) {
return null;
}
Node result = top;
top = top.next;
index--;
return result;
}
/**
* 獲取棧頂元素
*
* @return
*/
public Node top() {
return top;
}
@Data
@AllArgsConstructor
private class Node {
private T data;
private Node next;
}
public static void main(String[] args) {
OwnLinkedStack<String> stack = new OwnLinkedStack<String>();
for (int i = 0; i < DEFAULT_SIZE + 1; i++) {
System.out.println("push: " + stack.push(String.valueOf(i)) + ", item: " + i);
}
for (int i = 0; i < DEFAULT_SIZE + 1; i++) {
System.out.println("top: " + stack.top());
System.out.println("pop: " + stack.pop());
}
}
}
小提示:
入棧和出棧只會影響到最後一個元素,不涉及其他元素的整體移動,所以無論是以數組還是以鏈表實現,入棧、出棧的時間複雜度都是O(1)
隊列
隊列(queue)是一種線性數據結構,它的特徵和行駛車輛的單行隧道很相似。不同於棧的先入後出,隊列中的元素只能先入先出(First In First Out,簡稱FIFO)。隊列的出口端叫作隊頭(front),隊列的入口端叫作隊尾(rear)。
與棧類似,隊列這種數據結構既可以用數組來實現,也可以用鏈表來實現。
隊列的數組實現如下。
隊列的鏈表實現如下。
隊列基本操作
對於鏈表實現方式,隊列的入隊、出隊操作和棧是大同小異的。但對於數組實現方式 來說,隊列的入隊和出隊操作有了一些有趣的變化。
入隊
入隊(enqueue)就是把新元素放入隊列中,只允許在隊尾的位置放入元素,新元素 的下一個位置將會成爲新的隊尾。
出隊
出隊操作(dequeue)就是把元素移出隊列,只允許在隊頭一側移出元素,出隊元素的後一個元素將會成爲新的隊頭。
如果像這樣不斷出隊,隊頭左邊的空間失去作用,那隊列的容量豈 不是越來越小了?例如像下面這樣。
爲了解決這個問題,我們可以用數組實現的隊列可以採用循環隊列的方式來維持隊列容量的恆定。
假設一個隊列經過反覆的入隊和出隊操作,還剩下2個元素,在“物理”上分佈於數組 的末尾位置。這時又有一個新元素將要入隊。
在數組不做擴容的前提下,如何讓新元素入隊並確定新的隊尾位置呢?我們可以利用 已出隊元素留下的空間,讓隊尾指針重新指回數組的首位。
這樣一來,整個隊列的元素就“循環”起來了。在物理存儲上,隊尾的位置也可以在隊 頭之前。當再有元素入隊時,將其放入數組的首位,隊尾指針繼續後移即可。
一直到(隊尾下標+1)%數組長度 = 隊頭下標時,代表此隊列真的已經滿了。需要 注意的是,隊尾指針指向的位置永遠空出1位,所以隊列最大容量比數組長度小1。
循環隊列代碼實現
public class OwnQueue {
private int[] array;
private int front;
private int rear;
public OwnQueue(int capacity){
this.array = new int[capacity];
}
/**
* 入隊
* @param element 入隊的元素
*/
public void enQueue(int element) throws Exception {
if((rear+1)%array.length == front){
throw new Exception("隊列已滿!");
}
array[rear] = element;
rear =(rear+1)%array.length;
}
/**
* 出隊
*/
public int deQueue() throws Exception {
if(rear == front){
throw new Exception("隊列已空!");
}
int deQueueElement = array[front];
front =(front+1)%array.length;
return deQueueElement;
}
/**
* 輸出隊列
*/
public void output(){
for(int i=front; i!=rear; i=(i+1)%array.length){
System.out.println(array[i]);
}
}
public static void main(String[] args) throws Exception {
OwnQueue myQueue = new OwnQueue(6);
myQueue.enQueue(3);
myQueue.enQueue(5);
myQueue.enQueue(6);
myQueue.enQueue(8);
myQueue.enQueue(1);
myQueue.deQueue();
myQueue.deQueue();
myQueue.deQueue();
myQueue.enQueue(2);
myQueue.enQueue(4);
myQueue.enQueue(9);
myQueue.output();
}
}
循環隊列不但充分利用了數組的空間,還避免了數組元素整體移動的麻煩,可以說是很巧妙了。
本篇博客中代碼和彩圖來源於《漫畫算法》,應本書作者要求,加上本書公衆號《程序員小灰》二維碼。
感興趣的朋友可以去購買正版實體書,確實不錯,非常適合小白入門。
小結
- 什麼是棧
棧是一種線性邏輯結構,可以用數組實現,也可以用鏈表實現。棧包含入棧和出棧操 作,遵循先入後出的原則(FILO)。
- 什麼是隊列
隊列也是一種線性邏輯結構,可以用數組實現,也可以用鏈表實現。隊列包含入隊和 出隊操作,遵循先入先出的原則(FIFO)。
下一篇博客將爲大家介紹散列表,敬請期待!!!
如果本文對您有所幫助,不妨點個贊支持一下博主🙏
希望我們都能在學習的道路上越走越遠😉