zoukankan      html  css  js  c++  java
  • 数据结构之队列(Queue)

    什么是队列(Queue)

    之前总结过相关的知识,队列可以类比栈来看。栈只能在一端进行操作(栈顶),添加元素或者删除等都只能在栈顶;而队列有两端可以操作,在一端添加元素,在另一端删除元素

    我们把添加元素的一端称为队尾;删除元素的一端称为队首

    比如生活中的排队:城市中基本哪里都有,这就是一个队列。在队伍最前面就是队首,也是最先完成离开队伍的。新来的只能在队尾加入。

    queue_overview

    队列的特点:

    • [ ] 相同数据类型元素构成的有序表(和栈一样)。
    • [ ] 先进先出(FIFO:First In First Out)
    • [ ] 队首删除元素,队尾插入元素。

    队列操作及实现概述

    实现

    用链表、数组都比较容易实现队列。

    链表实现:比较简单,在链表首部删除,链表尾部插入即可。
    链表没有长度限制,不会造成溢出,而且插入删除也很简单,是很适合的实现方式。

    数组实现:普通数组方式,有着明显的缺陷,长度固定、出队时剩下所有元素都要向前挪动一位,这种出队方式时间复杂度就是O(n)了。在此基础上进行一些处理,形成一个环,成为一个循环队列

    所以下面主要介绍的就是两种队列实现,用链表实现的队列 和 用数组实现的循环队列

    操作

    队列主要两个操作,入队和出队。
    入队(enQueue): 即在队尾插入新元素。
    出队(deQueue): 即在队首删除一个元素。
    如果自己实现,可以添加更多公共操作,如队长、是否队空或队满、查找等等。

    下面实现中,为了更直观的显示出队、入队操作,都添加了一个displayQueue()的方法。

    单链表队列实现

    单链表队列实现很简单,链表首部即队首,链表尾部即队尾,定义两个引用分别指向队首和队尾,出队、入队直接删除和插入即可。时间复杂度为O(1)。
    优势,没有额外的操作,没有长度的限制 不需担心溢出。

    示意图大致如下:
    queue_linked

    具体代码如下(可结合注释,应该比较清晰):

    public class QueueLinkedList<E> {
    	
    	public static void main(String[] args) {
    	    //入队1,2,3,4,5;然后出队2次
    		QueueLinkedList<Integer> queueLinkedList = new QueueLinkedList<Integer>();
    		queueLinkedList.displayQueue();
    		for (int i = 1; i < 6; i++) {
    			queueLinkedList.enQueue(Integer.valueOf(i));
    		}
    		queueLinkedList.displayQueue();
    		queueLinkedList.deQueue();
    		queueLinkedList.displayQueue();
    		queueLinkedList.deQueue();
    		queueLinkedList.displayQueue();
    	}
    	
    	//定义队首引用,指向链表首部
    	private QueueNode<E> front;
    	//定义队尾引用,指向链表尾部
    	private QueueNode<E> rear;
    	
    	//节点定义
    	static class QueueNode<E> {
    		E data;
    		QueueNode<E> next;
    		
    		public QueueNode(E data) {
    			this.data = data;
    		}
    	}
    	
    	//初始化,空的队列。
    	public QueueLinkedList() {
    		this.front = this.rear = null;
    	}
    	
    	//入队,链表尾部插入
    	public void enQueue(E value) {
    		QueueNode<E> newNode = new QueueNode<E>(value);
    		//空队列时
    		if (this.rear == null) {
    			this.front = this.rear = newNode;
    			return;
    		}
    		this.rear.next = newNode;
    		this.rear = newNode;
    	}
    	
    	//出队,链表首部删除
    	public E deQueue() {
    		//队列为空时
    		if (this.front == null) 
    			return null;
    		E result = this.front.data;
    		this.front = this.front.next;
    		//队列中只有一个元素时
    		if (this.front == null) 
    			this.rear = null;
    		return result;
    	}
    	
    	//打印队列所有元素,以及队首、队尾信息
    	public void displayQueue() {
    		if (this.front == null) {
    			System.out.println("front:null<-...<-rear:null");
    			System.out.println("This is an empty queue!");
    			System.out.println();
    			return;
    		}
    		System.out.println("front:" + this.front.data +"<-...<-rear:" + this.rear.data);
    		QueueNode<E> tmpNode = this.front;
    		while (tmpNode != null) {
    			System.out.print(tmpNode.data + "<-");
    			tmpNode = tmpNode.next;
    		}
    		System.out.println();
    		System.out.println();
    	}
    }
    

    实验结果为:

    front:null<-...<-rear:null
    This is an empty queue!
    
    front:1<-...<-rear:5
    1<-2<-3<-4<-5<-
    
    front:2<-...<-rear:5
    2<-3<-4<-5<-
    
    front:3<-...<-rear:5
    3<-4<-5<-
    

    数组循环队列实现

    若用普通数组实现队列,就会发现每次删除队首时所有元素都需要向前移动一位,这样实现肯定是不合适的。
    于是,在上述基础上做一些处理,便形成了循环队列:将数组队列看出一个首尾相连的环形。用front和rear表示队首和队尾相关元素的下标(不一定是front是队首下标,rear是队尾下表),这样就能很容易确定队列的元素是哪些了。出队、入队改变这两个值即可而不需要移动数组中元素了。

    下面实现用变量front和rear标识队首、队尾的下标,入队和出队的方式基本一致:数组大小为capacity,即front=(font+1)%capacity 或 rear=(rear+1)%capacity。
    也可以用front标识队首,rear标识队尾的下一个位置,只是不同的标识含义, 初始化队列为空队列为满 的判断和处理需要注意,是不同的。 (rear标识队尾下一个位置时,初始化front=rear=0,空即front==rear。而rear标识队尾即下面的实现,可以具体看下)

    大致示意图如下(front和rear标识队首、队尾的下标):
    queue_circle_array

    实现代码(可结合注释查看):
    注:实现中的判空与判满 使用了另一个变量queueCount表示队列元素个数,便于返回队列大小。也可以使用front和rear之间关系进行判断

    public class QueueCircleArray {
    
    	public static void main(String[] args) {
    	    //队列入队1,2,3,4;然后两次出队;然后入队10,20
    		QueueCircleArray queueCircleArray = new QueueCircleArray(4);
    		queueCircleArray.displayQueue();
    		for (int i = 1; i < 5; i++) {
    			queueCircleArray.enQueue(Integer.valueOf(i));
    		}
    		queueCircleArray.displayQueue();
    		queueCircleArray.deQueue();
    		queueCircleArray.deQueue();
    		queueCircleArray.displayQueue();
    		queueCircleArray.enQueue(Integer.valueOf(10));
    		queueCircleArray.enQueue(Integer.valueOf(20));
    		queueCircleArray.displayQueue();
    	}
    	
    	//数组及大小
    	private Object[] array;
    	private int capacity;
    	//队首 队尾对应数组中的下标
    	private int front, rear;
    	//队列的大小
    	private int queueCount;
    	
    	public QueueCircleArray() {
    		this(10);
    	}
    
    	public QueueCircleArray(int capacity) {
    		this.capacity = capacity;
    		this.front = queueCount = 0;
    		//注意这里初始化
    		this.rear = this.capacity - 1;
    		this.array = new Object[this.capacity];
    	}
    	
    	public boolean isFull() {
    		return this.queueCount == this.capacity;
    	}
    	
    	public boolean isEmpty() {
    		return this.queueCount == 0;
    	}
    	
    	public void enQueue(Object value) {
    		if (isFull())
    			throw new RuntimeException("Queue is Full!");
    		//队尾向后移动一位,即将插入数据的位置
    		this.rear = (this.rear+1)%this.capacity;
    		//插入数据,入队
    		this.array[this.rear] = value;
    		//队列大小加1
    		this.queueCount++;
    	}
    	
    	public Object deQueue() {
    		if (isEmpty()) 
    			throw new RuntimeException("Queue is Empty!");
    		Object tmpObject = this.array[this.front];
    		//队首向后移动一位,之前的位置即出队的元素。
    		this.front = (this.front+1)%this.capacity;
    		//队列大小减1
    		this.queueCount--;
    		return tmpObject;
    	}
    	
    	public void displayQueue() {
    		if (isEmpty()) {
    			System.out.println("front:null<-...<-rear:null");
    			System.out.println("This is an empty queue!");
    			System.out.println();
    			return;
    		}
    		int tmp = this.front;
    		System.out.println("front:["+ this.front + "]" + this.array[this.front] 
    				+"<-...<-rear:[" + this.rear + "]" + this.array[this.rear]);
    		while(tmp != this.rear) {
    			System.out.print(this.array[tmp] + "<-");
    			tmp = (tmp + 1)%this.capacity;
    		}
    		if (tmp == this.rear)
    			System.out.println(this.array[tmp]);
    		System.out.println();
    	}
    }
    

    实验结果:

    front:null<-...<-rear:null
    This is an empty queue!
    
    front:[0]1<-...<-rear:[3]4
    1<-2<-3<-4
    
    front:[2]3<-...<-rear:[3]4
    3<-4
    
    front:[2]3<-...<-rear:[1]20
    3<-4<-10<-20
    
  • 相关阅读:
    StrutsTestCase 试用手记
    java版的SHA1
    看看junit在一个具体的项目中
    store/index.js中引入并注册modules目录中的js文件:require.context
    vue项目报错:$ is not defined
    状态合并:replaceState
    路由导航守卫中document.title = to.meta.title的作用
    vue路由中meta的作用
    BCryptPasswordEncoder加密与MD5加密
    滑块验证机制
  • 原文地址:https://www.cnblogs.com/fanglongxiang/p/13121836.html
Copyright © 2011-2022 走看看