資料結構與演算法—佇列圖文詳解
阿新 • • 發佈:2019-08-16
前言
棧和佇列
是一對好兄弟,前面我們介紹過資料結構與演算法—棧詳解,那麼棧的機制相對簡單,後入先出,就像進入一個狹小的山洞,山洞只有一個出口,只能後進先出(在外面的先出去)。而佇列就好比是一個隧道,後面的人跟著前面走,前面人先出去(先入先出)。日常的排隊
就是佇列運轉形式的一個描述!- 所以佇列的
核心理念
就是:先進先出! - 佇列的概念:佇列是一種特殊的
線性表
,特殊之處在於它只允許在表的前端(front)
進行刪除操作,而在表的後端(rear)
進行插入操作,和棧一樣,佇列是一種操作受限制
的線性表。進行插入操作的端稱為隊尾
,進行刪除操作的端稱為隊頭
。 - 同時,閱讀本偏文章最好先弄懂順序表的基本操作和棧的資料結構!學習效果更佳!
佇列介紹
基本屬性
隊頭front:
- 刪除資料的一端。對於陣列,
從後面插入更容易,前面插入較困難
,所以一般用陣列實現的佇列隊頭在前面。(刪除直接index遊標前進,不超過隊尾即可)。而對於連結串列。插入刪除在兩頭分別進行
那麼頭部(前面)刪除尾部插入
是最方便的選擇。
隊尾rear:
- 插入資料的一端,同上,在陣列和連結串列中
通常均在尾部位置
。當然,其實陣列和連結串列的front和rear還有點小區別,後面會具體介紹。
enQueue(入隊):
- 在
隊尾
rear插入元素
deQueue(出隊):
- 在
對頭
front刪除元素
普通佇列
按照上述的介紹,我們很容易知道陣列實現的方式。用陣列模擬
表示佇列。要考慮初始化,插入,問題。
- 初始化:陣列的front和rear都指向0.
- 入隊:
隊不滿
,陣列不越界
,先隊尾位置傳值,再隊尾下標+1 - 出隊:隊不空,先取隊頭位置元素,在隊頭+1,
但是很容易發現問題,每個空間域只能利用一次
。造成空間
極度浪費
。並且非常容易越界
!
迴圈佇列
針對上述的問題。有個較好的解決方法!就是對已經申請的(陣列)記憶體
重複利用
。這就是我們所說的迴圈佇列。
而陣列實現的迴圈佇列就是在邏輯上
稍作修改。我們假設
(約定)陣列的最後一位的下一個index是首位。因為我們佇列中只需要front和tail兩個指標。不需要陣列的實際地址位置相關資料。和它無關。所以我們就只需要考慮尾部的特殊操作即可。
- 初始化:陣列的front和rear都指向0.
- 入隊:
隊
不滿,先隊尾位置傳值,再rear=(rear + 1) % maxsize;
- 出隊:隊不空,先取隊頭位置元素,
front=(front + 1)%maxsize;
- 是否為空:
return rear == front;
- 大小:
return (rear+maxsize-front)%maxsize;
這裡面有幾個大家需要注意的,就是指標相加如果遇到最後需要轉到頭的話。可以判斷是否到陣列末尾位置。也可以直接+1求餘。其中maxsize
是陣列實際大小。
鏈式實現
對於連結串列實現的佇列,要根據
先進先出
的規則考慮頭和尾的位置
我們知道佇列是先進先出的,對於連結串列,我們能採用單鏈表儘量採用單鏈表,能方便儘量方便,同時還要兼顧效率
。
-
方案一 如果隊頭設在
連結串列尾
,隊尾設在連結串列頭
。那麼隊尾進隊插入在連結串列頭部插入沒問題。容易實現,但是如果隊頭刪除在尾部進行,如果不設定尾指標要遍歷到隊尾,但是設定尾指標刪除需要將它指向前驅節點
那麼就需要雙向連結串列。都挺麻煩的。 -
方案二但是如果隊頭設在
連結串列頭
,隊尾設在連結串列尾部
,那麼隊尾進隊插入在連結串列尾部插入沒問題(用尾指標可以直接指向next)。容易實現,如果隊頭刪除在頭部進行也很容易,就是我們前面常說的頭節點刪除節點。 -
所以我們
最終採取
的是方案2
的帶頭節點
,帶尾指標
的單鏈表!
主要操作為:
- 初始化:
public class listQueue<T> {
static class node<T> {
T data;// 節點的結果
node next;// 下一個連線的節點
public node() {}
public node(T data) {
this.data = data;
}
}
node front;//相當於head 帶頭節點的
node rear;//相當於tail/end
public listQueue() {
front=new node<T>();
rear=front;
}
- 入隊:
rear.next=va;rear=va
;(va為被插入節點)
- 出隊:隊不空,
front.next=front.next.next;
經典帶頭節點刪除
- 是否為空:
return rear == front;
- 大小:節點front遍歷到rear的個數。
具體實現
陣列實現
package 隊棧;
public class seqQueue<T> {
private T data[];// 陣列容器
private int front;// 頭
private int rear;// 尾
private int maxsize;// 最大長度
public seqQueue(int i)// 設定長為i的int 型佇列
{
data = (T[]) new Object[i+1];
front = 0;
rear = 0;
maxsize = i+1;
}
public int lenth() {
return (rear+maxsize-front)%maxsize;
}
public boolean isempty() {
return rear == front;
}
public boolean isfull() {
return (rear + 1) % maxsize == front;
}
public void enQueue(T i) throws Exception// 入隊
{
if (isfull())
throw new Exception("已滿");
else {
data[rear] = i;
rear=(rear + 1) % maxsize;
}
}
public T deQueue() throws Exception// 出隊
{
if (isempty())
throw new Exception("已空");
else {
T va=data[front];
front=(front+1)%maxsize;
return va;
}
}
public String toString()// 輸出隊
{
String va="隊頭: ";
int lenth=lenth();
for(int i=0;i<lenth;i++)
{
va+=data[(front+i)%maxsize]+" ";
}
return va;
}
}
鏈式實現
package 隊棧;
public class listQueue<T> {
static class node<T> {
T data;// 節點的結果
node next;// 下一個連線的節點
public node() {}
public node(T data) {
this.data = data;
}
}
node front;//相當於head 帶頭節點的
node rear;//相當於tail/end
public listQueue() {
front=new node<T>();
rear=front;
}
public int lenth() {
int len=0;
node team=front;
while(team!=rear)
{
len++;team=team.next;
}
return len;
}
public boolean isempty() {
return rear == front;
}
public void enQueue(T value) // 入隊.尾部插入
{
node va=new node<T>(value);
rear.next=va;
rear=va;
}
public T deQueue() throws Exception// 出隊
{
if (isempty())
throw new Exception("已空");
else {
T va=(T) front.next.data;
front.next=front.next.next;
return va;
}
}
public String toString()
{
node team=front.next;
String va="隊頭: ";
while(team!=null)
{
va+=team.data+" ";
team=team.next;
}
return va;
}
}
測試
總結
- 對於佇列來說資料結構
相比棧複雜一些
,但是也不是很難,搞懂先進先出
然後就用陣列或者連結串列實現即可。 - 對於陣列,隊尾
tail
指向的位置是空的,而連結串列的front
(head一樣)為頭指標為空的,所以在不同結構實現相同效果的方法需要注意一下。 - 對於雙向佇列,大家可以自行了解,雙向佇列兩邊均可插入刪除,能夠實現
堆疊公用
等更加靈活呼叫的結果。(參考java的ArrayDeque
).並且現在的訊息佇列等很多中介軟體都是基於佇列模型延申。所以學會佇列很重要! - 最後,筆者水平有限,如果有
紕漏和不足之處
還請指出。另外,如果感覺不錯可以點個贊,關注個人公眾號:bigsai
更多經常與你分享,關注回覆資料結構獲取精心
準備的資料結構和演算法資料多份!