帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之隊列
1、隊列的基本概念
隊列(queue
)是一種特殊的線性表,特殊之處在于它只允許在表的前端(front)進行刪除操作,而在表的后端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭。隊列中沒有元素時,稱為空隊列。
隊列的數(shù)據(jù)元素又稱為隊列元素。在隊列中插入一個隊列元素稱為入隊,從隊列中刪除一個隊列元素稱為出隊。因為隊列只允許在一端插入,在另一端刪除,所以只有最早進入隊列的元素才能最先從隊列中刪除,故隊列又稱為先進先出(FIFO—first in first out)線性表。
比如我們?nèi)ル娪霸号抨犢I票,第一個進入排隊序列的都是第一個買到票離開隊列的人,而最后進入排隊序列排隊的都是最后買到票的。
在比如在計算機操作系統(tǒng)中,有各種隊列在安靜的工作著,比如打印機在打印列隊中等待打印。
隊列分為:
①、單向隊列(Queue
):只能在一端插入數(shù)據(jù),另一端刪除數(shù)據(jù)。
②、雙向隊列(Deque
):每一端都可以進行插入數(shù)據(jù)和刪除數(shù)據(jù)操作。
這里我們還會介紹一種隊列——優(yōu)先級隊列,優(yōu)先級隊列是比棧和隊列更專用的數(shù)據(jù)結(jié)構(gòu),在優(yōu)先級隊列中,數(shù)據(jù)項按照關(guān)鍵字進行排序,關(guān)鍵字最?。ɑ蛘咦畲螅┑臄?shù)據(jù)項往往在隊列的最前面,而數(shù)據(jù)項在插入的時候都會插入到合適的位置以確保隊列的有序。
2、Java模擬單向隊列實現(xiàn)
在實現(xiàn)之前,我們先看下面幾個問題:
①、與棧不同的是,隊列中的數(shù)據(jù)不總是從數(shù)組的0下標開始的,移除一些隊頭front的數(shù)據(jù)后,隊頭指針會指向一個較高的下標位置,如下圖:
②、我們再設(shè)計時,隊列中新增一個數(shù)據(jù)時,隊尾的指針rear 會向上移動,也就是向下標大的方向。移除數(shù)據(jù)項時,隊頭指針 front 向上移動。那么這樣設(shè)計好像和現(xiàn)實情況相反,比如排隊買電影票,隊頭的買完票就離開了,然后隊伍整體向前移動。在計算機中也可以在隊列中刪除一個數(shù)之后,隊列整體向前移動,但是這樣做效率很差。我們選擇的做法是移動隊頭和隊尾的指針。
③、如果向第②步這樣移動指針,相信隊尾指針很快就移動到數(shù)據(jù)的最末端了,這時候可能移除過數(shù)據(jù),那么隊頭會有空著的位置,然后新來了一個數(shù)據(jù)項,由于隊尾不能再向上移動了,那該怎么辦呢?如下圖:
為了避免隊列不滿卻不能插入新的數(shù)據(jù),我們可以讓隊尾指針繞回到數(shù)組開始的位置,這也稱為“循環(huán)隊列”。
弄懂原理之后,Java實現(xiàn)代碼如下:
package com.ys.datastructure; public class MyQueue { private Object[] queArray; //隊列總大小 private int maxSize; //前端 private int front; //后端 private int rear; //隊列中元素的實際數(shù)目 private int nItems; public MyQueue(int s){ maxSize = s; queArray = new Object[maxSize]; front = 0; rear = -1; nItems = 0; } //隊列中新增數(shù)據(jù) public void insert(int value){ if(isFull()){ System.out.println("隊列已滿?。?!"); }else{ //如果隊列尾部指向頂了,那么循環(huán)回來,執(zhí)行隊列的第一個元素 if(rear == maxSize -1){ rear = -1; } //隊尾指針加1,然后在隊尾指針處插入新的數(shù)據(jù) queArray[++rear] = value; nItems++; } } //移除數(shù)據(jù) public Object remove(){ Object removeValue = null ; if(!isEmpty()){ removeValue = queArray[front]; queArray[front] = null; front++; if(front == maxSize){ front = 0; } nItems--; return removeValue; } return removeValue; } //查看對頭數(shù)據(jù) public Object peekFront(){ return queArray[front]; } //判斷隊列是否滿了 public boolean isFull(){ return (nItems == maxSize); } //判斷隊列是否為空 public boolean isEmpty(){ return (nItems ==0); } //返回隊列的大小 public int getSize(){ return nItems; } }
測試:
package com.ys.test; import com.ys.datastructure.MyQueue; public class MyQueueTest { public static void main(String[] args) { MyQueue queue = new MyQueue(3); queue.insert(1); queue.insert(2); queue.insert(3);//queArray數(shù)組數(shù)據(jù)為[1,2,3] System.out.println(queue.peekFront()); //1 queue.remove();//queArray數(shù)組數(shù)據(jù)為[null,2,3] System.out.println(queue.peekFront()); //2 queue.insert(4);//queArray數(shù)組數(shù)據(jù)為[4,2,3] queue.insert(5);//隊列已滿,queArray數(shù)組數(shù)據(jù)為[4,2,3] } }
3、雙端隊列
雙端隊列就是一個兩端都是結(jié)尾或者開頭的隊列,隊列的每一端都可以進行插入數(shù)據(jù)項和移除數(shù)據(jù)項,這些方法可以叫做:
insertRight()
、insertLeft()
、removeLeft()
、roveRight()
如果嚴格禁止調(diào)用insertLeft()
和removeLeft()
(或禁用右端操作),那么雙端隊列的功能就和前面講的棧功能一樣。
如果嚴格禁止調(diào)用insertLeft()
和removeRight(
或相反的另一對方法),那么雙端隊列的功能就和單向隊列一樣了。
4、優(yōu)先級隊列
優(yōu)先級隊列(priority queue
)是比棧和隊列更專用的數(shù)據(jù)結(jié)構(gòu),在優(yōu)先級隊列中,數(shù)據(jù)項按照關(guān)鍵字進行排序,關(guān)鍵字最小(或者最大)的數(shù)據(jù)項往往在隊列的最前面,而數(shù)據(jù)項在插入的時候都會插入到合適的位置以確保隊列的有序。
優(yōu)先級隊列 是0個或多個元素的集合,每個元素都有一個優(yōu)先權(quán),對優(yōu)先級隊列執(zhí)行的操作有:
(1)查找
(2)插入一個新元素
(3)刪除
一般情況下,查找操作用來搜索優(yōu)先權(quán)最大的元素,刪除操作用來刪除該元素 。對于優(yōu)先權(quán)相同的元素,可按先進先出次序處理或按任意優(yōu)先權(quán)進行。
這里我們用數(shù)組實現(xiàn)優(yōu)先級隊列,這種方法插入比較慢,但是它比較簡單,適用于數(shù)據(jù)量比較小并且不是特別注重插入速度的情況。
后面我們會講解堆,用堆的數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)優(yōu)先級隊列,可以相當快的插入數(shù)據(jù)。
數(shù)組實現(xiàn)優(yōu)先級隊列,聲明為int類型的數(shù)組,關(guān)鍵字是數(shù)組里面的元素,在插入的時候按照從大到小的順序排列,也就是越小的元素優(yōu)先級越高。
package com.ys.datastructure; public class PriorityQue { private int maxSize; private int[] priQueArray; private int nItems; public PriorityQue(int s){ maxSize = s; priQueArray = new int[maxSize]; nItems = 0; } //插入數(shù)據(jù) public void insert(int value){ int j; if(nItems == 0){ priQueArray[nItems++] = value; }else{ j = nItems -1; //選擇的排序方法是插入排序,按照從大到小的順序排列,越小的越在隊列的頂端 while(j >=0 && value > priQueArray[j]){ priQueArray[j+1] = priQueArray[j]; j--; } priQueArray[j+1] = value; nItems++; } } //移除數(shù)據(jù),由于是按照大小排序的,所以移除數(shù)據(jù)我們指針向下移動 //被移除的地方由于是int類型的,不能設(shè)置為null,這里的做法是設(shè)置為 -1 public int remove(){ int k = nItems -1; int value = priQueArray[k]; priQueArray[k] = -1;//-1表示這個位置的數(shù)據(jù)被移除了 nItems--; return value; } //查看優(yōu)先級最高的元素 public int peekMin(){ return priQueArray[nItems-1]; } //判斷是否為空 public boolean isEmpty(){ return (nItems == 0); } //判斷是否滿了 public boolean isFull(){ return (nItems == maxSize); } }
insert() 方法,先檢查隊列中是否有數(shù)據(jù)項,如果沒有,則直接插入到下標為0的單元里,否則,從數(shù)組頂部開始比較,找到比插入值小的位置進行插入,并把 nItems 加1.
remove 方法直接獲取頂部元素。
優(yōu)先級隊列的插入操作需要 O(N)的時間,而刪除操作則需要O(1) 的時間,后面會講解如何通過 堆 來改進插入時間。
5、總結(jié)
本篇博客我們介紹了隊列的三種形式,分別是單向隊列、雙向隊列以及優(yōu)先級隊列。其實大家聽名字也可以聽得出來他們之間的區(qū)別,單向隊列遵循先進先出的原則,而且一端只能插入,另一端只能刪除。雙向隊列則兩端都可插入和刪除,如果限制雙向隊列的某一段的方法,則可以達到和單向隊列同樣的功能。最后優(yōu)先級隊列,則是在插入元素的時候進行了優(yōu)先級別排序,在實際應(yīng)用中單項隊列和優(yōu)先級隊列使用的比較多。后面講解了堆這種數(shù)據(jù)結(jié)構(gòu),我們會用堆來實現(xiàn)優(yōu)先級隊列,改善優(yōu)先級隊列插入元素的時間。
通過前面講的棧以及本篇講的隊列這兩種數(shù)據(jù)結(jié)構(gòu),我們稍微總結(jié)一下:
①、棧、隊列(單向隊列)、優(yōu)先級隊列通常是用來簡化某些程序操作的數(shù)據(jù)結(jié)構(gòu),而不是主要作為存儲數(shù)據(jù)的。
②、在這些數(shù)據(jù)結(jié)構(gòu)中,只有一個數(shù)據(jù)項可以被訪問。
③、棧允許在棧頂壓入(插入)數(shù)據(jù),在棧頂彈出(移除)數(shù)據(jù),但是只能訪問最后一個插入的數(shù)據(jù)項,也就是棧頂元素。
④、隊列(單向隊列)只能在隊尾插入數(shù)據(jù),對頭刪除數(shù)據(jù),并且只能訪問對頭的數(shù)據(jù)。而且隊列還可以實現(xiàn)循環(huán)隊列,它基于數(shù)組,數(shù)組下標可以從數(shù)組末端繞回到數(shù)組的開始位置。
⑤、優(yōu)先級隊列是有序的插入數(shù)據(jù),并且只能訪問當前元素中優(yōu)先級別最大(或最小)的元素。
⑥、這些數(shù)據(jù)結(jié)構(gòu)都能由數(shù)組實現(xiàn),但是可以用別的機制(后面講的鏈表、堆等數(shù)據(jù)結(jié)構(gòu))實現(xiàn)。
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
詳細介紹idea如何設(shè)置類頭注釋和方法注釋(圖文)
本篇文章主要介紹了idea如何設(shè)置類頭注釋和方法注釋(圖文),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12SpringBoot+MinIO實現(xiàn)對象存儲的示例詳解
MinIO?是一個基于Apache?License?v2.0開源協(xié)議的對象存儲服務(wù),它是一個非常輕量的服務(wù),可以很簡單的和其他應(yīng)用的結(jié)合,所以下面我們就來看看SpringBoot如何整合MinIO實現(xiàn)對象存儲吧2023-10-10SpringBoot2中使用@RequestHeader獲取請求頭的方法
springMVC/SpringBoot中提供了@RequestHeader注解用來獲取請求頭。本文就詳細的來介紹一下如何使用,感興趣的可以了解下2021-10-10springboot?vue項目管理后端實現(xiàn)接口新增
這篇文章主要為大家介紹了springboot?vue項目管理后端實現(xiàn)接口新增,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-05-05