队列(一种遵循先进先出原则的数据结构)

1. 队列(Queue)

与栈类似,队列也是最基本的数据结构之一。顾名思义,队列中数据的存储与排队相类似,遵循“先进先出”(First-In-First-Out, FIFO)的原则(都是文明人,插队肯定是不允许的,手动狗头)。

队列的流程

2. 队列的抽象数据类型(队列ADT)

方法功能描述
enqueue(x)若队列未满,则将元素x加到队列末尾,否则报错
dequeue()若队列非空,则将队列首元素移除,并将其返回,否则报错
getSize()返回当前队列中元素的数目
isEmpty()用于检测队列是否为空
front()若队列非空,则返回队列首元素(但并不移除),否则报错

3. 队列接口

/**
* 队列接口
*/
public interface Queue {
	void enqueue(Object obj);
	Object dequeue() throws ExceptionQueueEmpty;
	int getSize();
	boolean isEmpty();
	Object front() throws ExceptionQueueEmpty;
}

4. 利用数组实现队列

4.1 队列的实现

借助一个定长数组Q来存放对象,即可简单地实现队列。一种自然的办法就是仿照栈的实现,以 Q[0]作为队首,其它对象顺序往后存放。然而如此一来, 每次首元素出队之后,都需要将后续的所有元素向前顺移一个单元⎯⎯若队长为 n,这项工作需要O(n)时间,因此效率很低。

为了避免数组的整体移动,可以引入两个变量 front 和 rear,分别指向队列的头和尾,使数组构成一个循环结构。

public class ExceptionQueueEmpty extends RuntimeException {
	
	public ExceptionQueueEmpty(String error) {
		super(error);
	}
	
}
public class ExceptionQueueFull extends RuntimeException {

	public ExceptionQueueFull(String error) {
		super(error);
	}
	
}
/**
 * 定长数组实现队列
*/
public class ArrayQueue implements Queue {
	private static final int CAPACITY = 1024;
	private int capacity;
	private Object[] Q;
	
	private int front;
	private int rear;
	
	public ArrayQueue() {
		this(CAPACITY);
	}
	
	public ArrayQueue(int capacity) {
		this.capacity = capacity;
		Q = new Object[this.capacity];
		front = 0;
		rear = 0;
	}

	@Override
	public void enqueue(Object obj) throws ExceptionQueueFull {
		// 数组只剩下一个空闲单元时就判定队列已满(如数组满则front与rear相等),(front == rear) is true用于判定队列空
		if ((capacity - 1) == getSize()) {
			// 对于容量有限的队列需要考虑队列溢出的情况
			throw new ExceptionQueueFull("意外:队列溢出");
		}
		Q[rear] = obj;
		// 利用取余数实现数组的循环
		rear = (rear + 1) % capacity;
	}

	@Override
	public Object dequeue() throws ExceptionQueueEmpty {
		if (isEmpty()) {
			throw new ExceptionQueueEmpty("意外:队列空");
		}
		Object returnElement = Q[front];
		Q[front] = null;
		front = (front + 1) % capacity;
		return returnElement;
	}

	@Override
	public int getSize() {
		return ((capacity + rear - front) % capacity);
	}

	@Override
	public boolean isEmpty() {
		return (front == rear);
	}

	@Override
	public Object front() throws ExceptionQueueEmpty {
		if (isEmpty()) {
			throw new ExceptionQueueEmpty("意外:队列空");
		}
		return Q[front];
	}

}

4.2 利用数组实现队列的优势与缺点

  1. 优势:
    1) 实现简单
    2) 各方法的时间复杂度均为O(1)
  2. 缺点:
    1) 若数组容量为N,则空间复杂度为O(N),在队列中的元素较少时会造成空间的浪费
    2) 入队操作会有溢出的风险

5. 利用单链表实现队列

由于队列只在队首才有出队(需删除元素)操作,因此只需要让单链表的头节点为队首,末节点为队尾,即可高效的实现队列这一数据结构(单链表删除末节点的时间复杂度是O(n))。

5.1 队列的实现

/**
 * 单链表节点类
*/
public class Node {
	private Object element; // 数据对象
	private Node next; // 指向后继节点
	
	public Node() {
		this(null, null);
	}
	
	public Node(Object element, Node next) {
		this.element = element;
		this.next = next;
	}
	
	public Object getElement() {
		return element;
	}
	
	public Object setElement(Object element) {
		Object oldElememt = element;
		this.element = element;
		return oldElememt;
	}
	
	public Node getNext() {
		return next;
	}
	
	public void setNext(Node newNext) {
		next = newNext;
	}
}
/**
 * 利用单链表实现队列
*/
public class LinkedListQueue implements Queue {
	private Node head;
	private Node tail;
	private int size; // 队列中元素的数目
	
	public LinkedListQueue() {
		head = null;
		tail = null;
		size = 0;
	}

	@Override
	public void enqueue(Object obj) {
		Node tailNode = new Node(obj, null);
		// 若此前队列为空,则直接插入
		if (0 == size) {
			head = tailNode;
			tail = tailNode;
		} else {
			tail.setNext(tailNode);
			tail = tailNode;
		}
		size++;
	}

	@Override
	public Object dequeue() throws ExceptionQueueEmpty {
		if (isEmpty()) {
			throw new ExceptionQueueEmpty("意外:队列空");
		}
		Object returnElement = head.getElement();
		head = head.getNext();
		size--;
		// 如果出队后,队列已空,需将末节点置空
		if (isEmpty()) {
			tail = null;
		}
		return returnElement;
	}

	@Override
	public int getSize() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return (size == 0);
	}

	@Override
	public Object front() throws ExceptionQueueEmpty {
		if (isEmpty()) {
			throw new ExceptionQueueEmpty("意外:队列空");
		}
		return head.getElement();
	}

}

5.2 利用单链表实现队列的优势与缺点

  1. 优势:
    1) 队列中实际有n个元素,空间复杂度即为O(n),空间利用效率较高
    2) 入队操作不会有溢出的风险
  2. 缺点:
    1) 较数组实现来说,再实现的复杂度上略有增加
    2) 需要有一个变量由于记录队列中元素的数目

6.队列的应用

队列的应用十分广泛,无论是商店、剧院、机场还是银行、医院,凡是按照“先到的客户优先接受服务”原则的场合,都可以利用队列这一数据结构来编排相应的服务。

7. 双端队列

双端队列是队列的一种变型,顾名思义,就是前端与后端都支持插入和删除操作的队列,它可以利用链表来实现。但由于双端队列两端都有删除操作,如果使用单链表来实现的话,总会有一端的删除操作的时间复杂度为O(n)。

造成此现象的原因是,只有找到末节点的直接前驱节点(末节点的前一个节点)之后,才能对末节点实施删除操作。对于单链表,一节点并不能直接访问它的前一个节点,所以想要找到末节点的直接前驱节点必须进行一次遍历,即时间复杂度为O(n)。

解决此问题的一个有效的方法是选用双向链表来实现。双向链表的节点都可以直接访问到它的前驱节点和后驱节点,故对于两端点的删除操作,时间复杂度也仅为O(1)。

利用双向链表实现双端队列与单链表实现队列类似,此处不再赘述。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值