队列的概述
正如上次文章所述。队列的实现方式有很多种,其中一种就是使用最简单的数组来实现。还有使用链表的方式来实 现队列。这里简单回顾下:
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行 插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队 头。队列中没有元素时,称为空队列。 队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从 队列中删除一个队列元素称为出队。因 为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才 能最先从队列中删除,故队列又称为先 进先出(FIFO—first in first out)线性表。
02数组和链表的存储数据的特点
☑ 数组
在内存中,数组是一块连续的区域,申请的内存必须是连续的,而且数组申请的时候需要指定长度(也就是说需要 提前申请)。例如:申请了长度为10 ,但是如果里面只存储了5个元素。那么其他的内存就浪费了。
另外,数据的插入 和 删除 效率低下,因为数组是连续的,如果在插入的时候,就要求后续数据,在内存中往后移 动。例如(就像看电影座位有5个座位,中间有人坐了,如果来一个人坐中间,原本的人就要移动座位才行)扩展不容易,因为是提前申请内存空间,如果不够就没办法继续存储。如果申请太多,没用就是资源浪费了。
读取效率高,因为数组的地址是连续的,并且知道地址之后,很快就可以根据地址查询数据了。
☑ 链表
链表中申请的内存地址是随机的,可以不重复,也就意味着 可以充分利用资源,不会有像数组那样的连续内存。增加和删除数据 效率高,因为链表(包括单列表和双链表),有头结点和尾结点,从头部或者从尾部添加结点(数 据)的时候只需要记住原头不结点的地址或者是尾部结点的地址即可。很快便能申请内存地址。
扩展容易,因为不需要连续申请内存,数据可以存储在任意的内存地址中。
查询数据的时候效率低下,因为链表中的数据都是通过头来找到的,(要找链表中的一个数据,必须就是从头开始找起)。
03链表的队列实现
实现思路:
·创建一个双链表类
·创建一个以链表实现的队列类,比如实现Queue的队列接口
·在队列类中使用链表作为数据存储
·简单实现入队和出队的模拟
☑ 创建双链表类
package com.itheima.link;
import java.util.Comparator;
import java.util.Iterator;
/**
* 双链表
*
* @author
*
*/
public class MyDoubleLink2
/**
* 节点的封装类
*
* @author Administrator
*
*/
private class Node {
Node prev; // 上一个节点
E data;// 数据
Node next;// 下一个节点
}
private Node head;// 头节点
private Node rear;// 尾节点
/**
* 删除数据: 1.只有一个数据 2.删除头节点 3.删除尾节点 4.删除中间节点
*
* @param data
*/
public void remove(Object data) {
// 1. 查找数据是否存在,查找数据所在的节点
Node node = findData(data);
// 2.判断node是否存在
if (node != null) {
remove(node);
}
}
/**
* 是否有数据
* @return
*/
public boolean isEmpty(){
return head == null;
}
/**
* 移除尾部的数据
* @return
*/
public E removeRear(){
E data = null;
if (rear != null) {
data = rear.data;
//移除
rear = rear.prev;
if (rear == null) {
//没有节点
head = null;
} else {
rear.next = null;
}
}
return data;
}
/**
* 移除头部的数据
* @return
*/
public E removeHead(){
E data = null;
if (head != null){
data = head.data;
//下一个数据成为头节点
head = head.next;
//后面没有
if (head != null)
head.prev = null;
else {
//没有数据
rear = null;
}
}
return data;
}
/**
* 是否包含数据
*
* @param data
* @return
*/
public boolean contains(Object data) {
return findData(data) != null;
}
/**
* 删除节点
*
* @param node
*/
private void remove(Node node) {
if (node == head && node == rear) {
// 1.只有一个数据
head = null;
rear = null;
} else if (node == head) {
// 2.删除头节点,后面肯定有节点
head = head.next;
head.prev = null;
} else if (node == rear) {
// 3.删除尾节点,前面肯定有节点
rear = rear.prev;
rear.next = null;
} else {
// 4.删除中间节点,前后肯定有节点
node.prev.next = node.next;
node.next.prev = node.prev;
}
}
/**
* 查找数据所在的节点
*
* @param data
* @return
*/
private Node findData(Object data) {
Node node = head;
while (node != null) {
if (node.data.equals(data)
&& node.data.hashCode() == data.hashCode()) {
// 找到数据
break;
} else {
// 继续下一个
node = node.next;
}
}
return node;
}
/**
* 从头部添加数据
*
* @param data
*/
public void addHead(E data) {
// 1. 创建新的节点
Node node = new Node();
// 2. 把数据放入节点中
node.data = data;
// 3. 把节点链接到链表中
// 从链表的尾部添加数据
if (head == null) {
// 说明链表是空的 ,node就成头节点
rear = node;
head = node;
} else {
// 有头节点
head.prev = node;
node.next = head;
head = node;
}
}
/**
* 添加数据,从链表的尾部添加数据
*
* @param data
*/
public void add(E data) {
// 1. 创建新的节点
Node node = new Node();
// 2. 把数据放入节点中
node.data = data;
// 3. 把节点链接到链表中
// 从链表的尾部添加数据
if (rear == null) {
// 说明链表是空的 ,node就成头节点
rear = node;
head = node;
} else {
// 有头节点
rear.next = node;
node.prev = rear;
rear = node;
}
}
@Override
public String toString() {
StringBuilder mess = new StringBuilder("[");
// 遍历链表中所有的数据
Node node = head;// 从头节点开始遍历数据
while (node != null) {
// 有数据
// 拼接数据
// 判断是否是尾节点
if (node != rear) {
mess.append(node.data + ", ");
} else {
mess.append(node.data);
}
// 条件的改变
node = node.next;
}
mess.append("]");
return mess.toString();
}
@Override
public Iterator iterator() {
class MyIte implements Iterator {
private Node node = head;// 从头节点遍历
@Override
public boolean hasNext() {
return node != null;
}
@Override
public Object next() {
// 获取数据
Object data = node.data;
// 设置下一个数据
node = node.next;
return data;
}
@Override
public void remove() {
// 删除next方法返回的数据
MyDoubleLink2.this.remove(node.prev);
}
}
return new MyIte();
}
}
从尾部结点的数据方法如下:
public void add(E data) {
// 1. 创建新的节点
Node node = new Node();
// 2. 把数据放入节点中
node.data = data;
// 3. 把节点链接到链表中
// 从链表的尾部添加数据
if (rear == null) {
// 说明链表是空的 ,node就成头节点
rear = node;
head = node;
} else {
// 有头节点
rear.next = node;
node.prev = rear;
rear = node;
}
}
☑ 创建以链表存储数据的队列类
package com.itheima.queue;
import com.itheima.link.MyDoubleLink;
import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
/**
* 描述
*
* @author 三国的包子
* @version 1.0
* @package com.itheima.queue *
* @since 1.0
*/
public class MyLinkQueue
private MyDoubleLink
@Override
public boolean add(E e) {
datas.add(e);
return true;
}
@Override
public E poll() {
return datas.removeHead();
}
@Override
public String toString() {
return datas.toString();
}
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return datas.isEmpty();
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Iterator
return null;
}
@Override
public Object[] toArray() {
return new Object[0];
}
@Override
public
return null;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean containsAll(Collection> c) {
return false;
}
@Override
public boolean addAll(Collection extends E> c) {
return false;
}
@Override
public boolean removeAll(Collection> c) {
return false;
}
@Override
public boolean retainAll(Collection> c) {
return false;
}
@Override
public void clear() {
}
@Override
public boolean offer(E e) {
return false;
}
@Override
public E remove() {
return null;
}
@Override
public E element() {
return null;
}
@Override
public E peek() {
return null;
}
}
方法解析:
为了以后的扩展性 可以实现Queue的接口,此处为演示 只实现了其中的几个方法:
·add(E e) 添加数据(入队)
·poll() 移除数据(出队)
·isEmpty() 判断队列是否为空
·toString() 重写方法 便于打印
☑ 创建双链表的数据储存对象
在队列类中创建
private MyDoubleLink
☑ 入队方法
入队方法如下,参考刚才的方法。入队的元素从尾部结点添加即可。
@Override
public boolean add(E e) {
datas.add(e);
return true;
}
如下图所示:
解释:
·将原尾结点的NEXT指向新的结点
·将新结点的prev指向原尾结点
·将新结点作为尾结点(也就是将尾结点的引用指向那个新的结点)
理解图如下:
☑ 出队方法
@Override
public E poll() {
return datas.removeHead();
}
队列的方式是:先进先出,该方法表示从头开始删除节点。也就是出队。头先添加,最先出去的也是头结点。
如下图:
·先判断是否有头 如果有,那么将头结点引用指向下一个结点。
·再将头结点的prev赋值为空即可。
04总结
以上演示了队列的链表实现方式。
对于想快速访问,不经常进行插入和删除的操作 可以使用数组。对于经常进行添加和删除,对查询没有特别要求的可以使用链表。