前言
隊列是數據結構中最常見的一種,與此對應的還有棧。我前面寫過一篇使用java實現棧的,有興趣的可以點擊查看。學習的時候,應該大多數讀者都是使用c語言去實現,在數據結構書中一般也都是使用c語言去實現棧這種數據結構。確實,因爲c語言有指針能夠更好地操作內存,而且運行速度快很適合作爲寫數據結構地語言。但是因爲學習了java,所以今天也來嘗試一下使用java來實現隊列。同時簡單介紹一下什麼是隊列。
什麼是隊列?
隊列是一種很長見的數據結構,他的模型也很簡單:像一個單行道的隧道,先進去的車先出來,“先進先出”就是他的特點。我們知道數據結構中的物理結構有順序和鏈式兩種,所以對應的就有順序隊列和鏈式隊列。這裏的順序隊列我只講循環隊列,首尾相接。其他類型的循環隊列有興趣的讀者可自行去了解。
順序隊列
順序隊列,使用數組去實現的。這裏簡單講一下循環隊列。
這裏的循環隊列的實現方式使用首尾相接的模式,每當出隊的時候,隊頭的標記位置+1,每當入隊的時候隊尾的標記位置+1.但現在就有一個問題了,假如我現在是一個長度爲10的數組,然後我的隊頭標記位置是8,隊尾是9,那當再次入隊的時候豈不是就溢出了?而且前面還有好多空間沒用,所以這個時候就要把整個數組首尾相接,隊尾從9過渡到0 可以看圖片理解一下:
然後再講一下如何通過隊頭和隊尾的位置來判斷隊列是否已經滿了呢?這裏涉及到一個簡單的算法,因爲他是循環的,假如maxsize是最大長度,front是隊頭位置,rear是隊尾位置,那麼(rear+1)%maxsize == front的時候,隊列滿了。可以通過上圖來理解。
這樣應該既可以理解這個循環隊列的意思了。
要建立一種數據結構,首先要定義接口:
定義接口
package Stucture;
public interface QueueApi<T> {
//初始化隊列
boolean initQueue(int maxSize);
//新元素入隊
boolean pushQueue(T t);
//隊頭元素出隊
T popQueue();
//獲取隊列隊頭元素
T getTop();
//獲取隊列的元素數目
int getElemNum();
//判斷隊列是否爲空
boolean isQueueEmpty();
//清空隊列
void clearQueue();
}
這裏一共定義了7個方法,使用了泛型以便可以對應不同的數據類型。
實現隊列類
定義了接口之後,那麼就得來寫隊列這個類了。上面的接口規定了隊列擁有的方法,然後還需要5個屬性:隊列中的數組,隊列的頭位置,隊列的尾位置,隊列的長度,隊列的元素數目。因爲java不允許建立泛型數據數組,關於爲什麼可以看我的另一篇文章學習筆記之java爲什麼不能新建泛型類型數據數組?,有興趣的讀者可以瞭解一下。所以我們必須先給他指定數據類型,我這裏定義了一個Student類:
package entity;
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
接下來看看完整的隊列類,然後我們再一個個來解析下,先看看代碼:
package Stucture;
import entity.Student;
public class StudentSequeueQueue implements QueueApi<Student> {
private Student[] studentQueue ;
private int front = 0;
private int rear = 0;
private int num = 0;
private int size;
//初始化隊列
@Override
public boolean initQueue(int maxSize) {
// TODO Auto-generated method stub
if (studentQueue == null) {
studentQueue = new Student[maxSize];
if(studentQueue == null) return false;
size = maxSize;
return true;
}else return false;
}
//元素入隊
@Override
public boolean pushQueue(Student t) {
// TODO Auto-generated method stub
if(studentQueue!=null&&(rear+1)%size != front) {
studentQueue[rear] = t;
num++;
if(rear+1 == size) rear = 0;
else rear++;
return true;
}else {
return false;
}
}
//隊頭元素出隊
@Override
public Student popQueue() {
// TODO Auto-generated method stub
if(num!=0) {
int position = front;
if(front+1!=size) front++;
else front = 0;
num--;
return studentQueue[position];
}
return null;
}
//獲取隊頭元素
@Override
public Student getTop() {
// TODO Auto-generated method stub
if(num!=0) return studentQueue[front];
else return null;
}
//獲取隊列中的元素數目
@Override
public int getElemNum() {
// TODO Auto-generated method stub
return num;
}
//判斷隊列是否爲空
@Override
public boolean isQueueEmpty() {
// TODO Auto-generated method stub
if(num==0) return true;
else return false;
}
//清空隊列
@Override
public void clearQueue() {
// TODO Auto-generated method stub
front = 0;
rear = 0;
num = 0;
}
}
代碼看起來挺多,不慌,一個一個來看:
- 屬性:這幾個屬性相信也很容易懂,主要是這個size,這裏我沒有指定數值,具體的隊列長度由外部初始化的時候指定。
- 初始化:這裏通過外部給的數目創建數組,如果創建成功返回true創建失敗返回false。
- 元素入隊:元素入隊的時候首先要注意一下隊列初始化了沒有;然後判斷一下隊列是否已經滿了;再把元素放進去,num+1;然後還需要再判斷一下是否到最大長度的地方了,是的話令rear = 0。
- 元素出隊:首先要判斷一下隊列中是否有元素;然後記錄隊頭的位置;再把隊頭的位置往前移,注意判斷是否到最大長度了。然後返回隊頭元素。
- 返回隊頭元素:和元素出隊類似。
- 獲取數目:返回num。
- 判斷是否爲空:判斷元素數目是否爲0.
- 清空隊列:把隊頭和隊尾位置、元素數目都置零。
這樣我們就把順序隊列寫好了。接下來測試一下:
測試
先看看代碼:
import Stucture.LinearQueue;
import Stucture.LinearStack;
import Stucture.StudentSequeneStack;
import Stucture.StudentSequeueQueue;
import entity.Student;
public class Main {
public static void main(String[] args) {
Student student1 = new Student();
student1.setAge(12);
student1.setName("mike");
Student student2 = new Student();
student2.setAge(13);
student2.setName("sali");
StudentSequeueQueue queue = new StudentSequeueQueue();
queue.initQueue(10);
queue.pushQueue(student1);
queue.pushQueue(student2);
System.out.println(queue.getTop().getName());
System.out.println(queue.getElemNum());
queue.popQueue();
System.out.println(queue.getTop().getName());
queue.popQueue();
for(int i = 0;i<=7;i++) queue.pushQueue(student1);
queue.pushQueue(student2);
System.out.println(queue.getElemNum());
System.out.println(queue.getTop().getName());
for(int i = 0;i<=7;i++) queue.popQueue();
System.out.println(queue.getTop().getName());
queue.clearQueue();
System.out.println(queue.getElemNum());
if (queue.getTop()==null) {
System.out.println("清空成功");
}
上面對我們剛剛寫的隊列進行了簡單的測試,看看測試結果:
可以看到順序隊列成功了。
鏈隊列
鏈隊列相比順序隊列而言不用考慮循環的問題,只要通過鏈表把每個元素串起來,鏈表頭出隊,入隊的時候接在鏈表尾。就可以實現鏈隊列了。
接口在順序隊列已經寫好了,就不用再寫一次了
節點類
和順序隊列不同的是,鏈隊列的每個節點都需要一個指向下一個元素的引用,每一個節點都需要兩個域:數據域和指針域。說的玄乎,但其實就是上面我說的一個是Student這個對象的引用一個是節點的引用。看代碼:
package Stucture;
//節點
public class Node<T> {
T t;
Node<T> elem;
}
這裏我用了泛型,這樣的話,對於不同的數據類型可以對應不同的泛型。例如我們上面的Student類,那麼我們就可以指定泛型爲Student。爲什麼這裏不像c語言那樣把具體的數據放進去,例如把name和age這兩個數據放進去而是放了一個Student引用呢?這其實和結構體是一樣的,外部只需要關注每個節點的數據是什麼而不需要關注什麼引用指針。外部只需要傳入Student這種對象就ok了,不用去關注其他的,這也是一種封裝的思想。那麼定好了節點類,接下來看看隊列類怎麼寫,先看看代碼在再一個個來解析
實現鏈隊列類
隊列類的屬性需要隊列頭的引用,隊列尾的引用,隊列的長度。實現隊列接口。先看看整體代碼,再來解析:
package Stucture;
public class LinearQueue<T> implements QueueApi<T> {
private Node<T> frontNode = null;
private Node<T> rearNode = null;
private int num = 0;
@Override
public void initQueue(int maxSize) {
// TODO Auto-generated method stub
}
//元素入隊
@Override
public boolean pushQueue(T t) {
// TODO Auto-generated method stub
Node<T> node = new Node<T>();
node.elem = t;
node.next = null;
if(rearNode == null) rearNode = node;
else {
rearNode.next = node;
rearNode = node;
}
if(frontNode == null) frontNode = node;
num++;
return true;
}
//元素出隊
@Override
public T popQueue() {
// TODO Auto-generated method stub
if(num!=0) {
Node<T> node;
node = frontNode;
frontNode = frontNode.next;
num--;
return node.elem;
}else {
return null;
}
}
//獲取隊頭元素
@Override
public T getTop() {
// TODO Auto-generated method stub
if(num!=0) return frontNode.elem;
else return null;
}
//獲取隊列中的元素數目
@Override
public int getElemNum() {
// TODO Auto-generated method stub
return num;
}
//判斷隊列是否是空的
@Override
public boolean isQueueEmpty() {
// TODO Auto-generated method stub
if(num == 0)return true;
else return false;
}
//清空隊列
@Override
public void clearQueue() {
// TODO Auto-generated method stub
if(num != 0) {
Node<T> node;
while(frontNode!=null) {
node = frontNode;
frontNode = frontNode.next;
node.next = null;
}
rearNode = null;
}
}
}
- 屬性:屬性也比較簡單,相當於c語言中的兩個指針,這裏兩個引用指向隊頭和隊尾,還有一個num統計長度。
- 初始化:因爲不想順序隊列那樣需要開闢空間,所以這裏不需要初始化。
- 元素入隊:首先要判斷是不是第一次入隊,如果是的話那麼隊尾和隊頭引用都要指向這個元素,不是的話原隊尾的引用要指向改元素節點,改元素節點的next指向null;再把num+1.
- 元素出隊:首先判斷是否爲空,不是的話返回隊頭元素,並把隊頭指向隊頭引用的next引用。
- 返回隊頭元素:和元素出隊類似。
- 獲取長度:返回num。
- 判斷隊列是否爲空:判斷長度是否爲0;
- 清空隊列:首先判斷隊列是否爲空,不是的話,把每個元素的next引用都指向null,再把隊頭引用和隊尾引用指向null。
鏈隊列的實現可能要比順序隊列要簡單一下,這樣的話就實現了鏈隊列了。看看測試:
測試一下
首先看看代碼:
import Stucture.LinearQueue;
import entity.Student;
public class Main {
public static void main(String[] args) {
Student student1 = new Student();
student1.setAge(12);
student1.setName("mike");
Student student2 = new Student();
student2.setAge(13);
student2.setName("sali");
LinearQueue<Student> linearQueue = new LinearQueue<Student>();
linearQueue.pushQueue(student1);
linearQueue.pushQueue(student2);
linearQueue.pushQueue(student1);
linearQueue.pushQueue(student1);
for(int i=0;i<3;i++) {
System.out.println(linearQueue.getTop().getName());
linearQueue.popQueue();
}
System.out.println(linearQueue.getElemNum());
linearQueue.clearQueue();
if(!linearQueue.isQueueEmpty()) System.out.println("清空成功");
}
}
然後來看看測試結果:
這樣我們的鏈隊列就成功實現了。
小結
隊列是數據結構中比較常見而且相對簡單的一種數據結構。在學習的時候看起來好像很簡單,但其實實際寫起來的話一開始還是有一點難以下手。特別是習慣了c語言的寫法,再轉思想用java寫可能會比較困難。但是當我們親手寫出來之後肯定印象會更加深的,也會更加了解這兩種語言之間的差別。上面實現的是簡單的隊列模型,有哪些地方不足讀者可以在評論區留言。
·
·
·
參考資料
《數據結構》吳偉民