数据结构之——队列

队列抽象数据类型

队列(queue)是一种特殊的线性表,其插入和删除操作分别在线性表的两端进行。向队列中插入元素的过程称为入队,删除元素的过程称为出队。允许入队的一端叫做队尾,允许出队的一端叫做队头。没有元素的队列称为空队列。

由于插入和删除分别在队尾和队头进行,最先入队的总是最先出队。因此队列的特点是“先进先出”。队列的基本操作有创建队列、判断队列是否为空、入队和出队。队列和栈一样,不支持对指定位置的插入和删除。

声明队列接口Queue< T >如下

package Queue;

public interface Queue<T> {
	public abstract boolean isEmpty();     //判断队列是否为空
	public abstract boolean add(T x);      //元素x入队,若添加成功,则返回true
	public abstract T peek();              //返回队头原速度,不做删除。若队列空,返回null
	public abstract T pool();              //出队,返回队头元素。若队列空,返回null

}

实现Queue< T >队列接口的类有顺序循环队列、链式循环队列和优先队列。

队列也有顺序和链式两种存储结构,分别称为顺序队列和链式队列。

顺序队列

1.顺序队列

(1)使用顺序表,出队效率低

如果使用一个顺序表作为队列的成员变量,入队操作执行顺序表尾插入,时间复杂度为0(1);出队操作执行顺序表头删除,时间复杂度都为O(n),效率较低。希望出队操作效率也是O(1),因此,不使用一个顺序表作为队列的成员变量。

(2)使用数组,存在假溢出

顺序队列使用数组存储数据元素,用front、rear记住队列头、尾元素下标,入队、出队时,改变front、rear取值,则不需要移动元素。我们举个例子来分析一下:

假设数组长度为5.

(a)队列初始空状态,令front和rear都为-1;

(b)10入队,此时front=rear=0;

(c)20和30入队,此时front=0,rear=2;

(d)10和20出队,此时front=rear=2;

(e)40和50入队,front=2,rear=4;

(f)60想要入队,front=2,rear=length=5,队尾下标越界,假溢出。

思路可描述如下:

(1)当队列空时,设置队头、队尾下标front和rear=-1.

(2)当第一个元素入队,front=rear=0,同时改变了两个下标。

(3)入队操作,元素存入rear位置,rear++。

(4)出队操作,返回front队头元素,front++。

(5)当入队的元素个数超过数组容量,rear下标越界,数据溢出,此时,由于之前已有若干元素出队,数组前部已多出许多存储单元,所以这种溢出不是因为存储空间不够,称为假溢出。

顺序队列存在两个缺点:假溢出以及一次入队/出队操作需要同时改变两个下标。为解决这个问题,我们可以把顺序队列设计成循环结构。

2.顺序循环队列

顺序循环队列就是逻辑上首尾相连的循环结构,这样可以连续使用存储单元。

设front是队头元素下标,rear是下一个入队元素下标

(1)设置初始空队列为font=rear=0,约定队列空的条件就是front=rear。

(2)入队操作改变rear,出队操作改变front,变化规律如下,其中length表示数组容量。

设length=5:

(a)初始化空队列,令front=rear=0;

(b)10和20入队。front=0,rear=2;

(c)10和20出队。front=rear=2;

(d)30、40、50、60入队。此时front=2,rear=1,队列满front==(rear+1)%length

(e)扩充容量后,front=0,rear=4.

(3)约定队列满条件是front==(rear+1)%length,此时队列中依然有一个空的位置,因为如果不保留一个位置,队列满的条件也是front==rear,与队列空条件相同。

(4)当队列满时再入队,将数组容量扩充一倍,按照队列元素次序复制数组元素。

3.顺序循环队列类

声明顺序循环队列类SeqQueue< T >如下,实现队列接口。

package Queue;

public class SeqQueue<T> implements Queue<T> {
	private Object element[];         //存储队列数据元素的数组
	private int front,rear;
	public SeqQueue(int length) {
		if(length<64) {
			length=64;
		}
		this.element=new Object[length];
		this.front=this.rear=0;      //设置空队列
	}
	public SeqQueue() {
		this(64);      //设置默认容量的队列
	}
	@Override
	public boolean isEmpty() {
		return this.front==this.rear;
	}

	@Override
	public boolean add(T x) {          //元素x入队,空对象不能入队
		if(x==null) {
			return false;
		}
		if(this.front==(this.rear+1)%this.element.length) {    //若队列满,则扩充数组
			Object[] temp=this.element;
			this.element=new Object[temp.length*2];    //重新申请一个容量更大的数组
			int j=0;
			for(int i=this.front;i!=this.rear;i=(i+1)%temp.length) {
				this.element[j]=temp[i];       //按照队列元素次序复制数组元素
				j++;
			}
			this.front=0;
			this.rear=j;
				
			
		}
		this.element[this.rear]=x;
		this.rear=(this.rear+1)%this.element.length;
		return true;
	}

	@Override
	public T peek() {      //返回队头元素,没有删除。若队列空,则返回nu
		return this.isEmpty()?null:(T)this.element[this.front];
	}

	@Override
	public T pool() {      //出队,返回队头元素。若队空则返回null
		if(this.isEmpty()) {
			return null;
		}
		T temp=(T)this.element[this.front];
		this.front=(this.front+1)%this.element.length;
		return temp;
		
	}
	

}

链式队列

(1)使用单链表,入队效率低

1.使用一个单链表作为队列的成员变量,入队操作执行单链表尾插入,时间复杂度为O(n),效率较低;出队操作执行单链表头删除,时间复杂度为O(1)。因此,不能用一个单链表作为队列的成员变量,因为入队操作达不到O(1)。

2.使用一个循环双链表作为队列的成员变量,入队操作执行循环双链表尾插入,出队操作执行循环双链表头删除,时间复杂度都为O(1),但占用较多的空间。

(2)单链表设计,增加尾指针

以下为单链表(不带头结点)实现链式队列的过程。设front和rear分别指向队头和队尾结点,增加一个尾指针,即可使入队和出队操作的空间复杂度都是O(1).

1.设置初始空队列,front=rear=null,队列空条件是front == null&&rear==null。

2.入队操作,将x结点链在rear之后,并使rear指向x结点成为新的队尾。

3.出队操作,当队列不空时,取得队头结点元素,删除队头结点,并使front指向后继结点。

4.当第一个元素入队或最后一个元素出队时,同时改变front和rear。

声明链式队列类LinkedQueue如下,实现队列接口,其中成员变量front和rear分别指向队列头结点和尾节点,数据类型都是单链表结点类Node。

package Queue;
import dataStructure.Node;

public final class LinkedQueue<T> implements Queue<T> {
	private Node<T> front,rear;        //front和rear分别指向队头和队尾结点
	public LinkedQueue() {				   //构造空队列
		this.front=this.rear=null;
	}
	@Override
	public boolean isEmpty() {
		return this.front==null&&this.rear==null;
	}

	@Override
	public boolean add(T x) {
		if(x==null) {
			return false;
		}
		Node<T> q=new Node<T>(x,null);
		if(this.front==null) {
			this.front=q;		//空队插入
			
		}else {
			this.rear.next=q;        //队列尾插入
		}
		this.rear=q;
		return true;
	}

	@Override
	public T peek() {       //返回队头元素,没有删除
		
		return this.isEmpty()?null:this.front.data;
	}

	@Override
	public T pool() {
		if(isEmpty()) {
			return null;
		}
		T x=this.front.data;    //取得队头元素
		this.front=this.front.next;    //删除队头结点
		if(this.front==null) {
			this.rear=null;
		}
		return x;
	}
	

}

结点Node类:

package dataStructure;

public class Node<T> {
	public T data;//数据域,存储数据元素
	public Node<T> next; //地址域,引用后继结点
	public Node(T data,Node<T> next) {
		this.data=data;
		this.next=next;
	}
	public Node() {
		this(null, null);
	}
	public String toString() { //返回结点数据域的描述字符串
		return this.data.toString();
	}

}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值