文章目录
- 前言
- 什么是队列?
- 顺序队列
- 定义接口
- 实现队列类
- 测试
- 链队列
- 节点类
- 实现链队列类
- 测试一下
- 小结
- 参考资料
前言
队列是数据结构中最常见的一种,与此对应的还有栈。我前面写过一篇使用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写可能会比较困难。但是当我们亲手写出来之后肯定印象会更加深的,也会更加了解这两种语言之间的差别。上面实现的是简单的队列模型,有哪些地方不足读者可以在评论区留言。
·
·