当前位置:首页 > 技术分析 > 正文内容

先进先出的数据结构——队列

ruisui881个月前 (05-16)技术分析10



一、什么是队列

队列(queue)是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,队列是一种操作受限制的线性表。

进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

队列的数据元素又称为队列元素,在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。

因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。


队列分为:

  1. 单向队列(Queue):只能在一端插入数据,另一端删除数据。
  2. 双向队列(Deque):每一端都可以进行插入数据和删除数据操作。

队列的实现有两种方式:数组实现和链表实现,其中用数组实现的队列有两种:一种是顺序队列,另一种是循环队列。



二、队列的存储结构

用数组实现,初始化一个队列长度为6,队列有两个标记,一个队头的位置head,一个队尾的位置tail,初始都指向数组下标为0的位置

在插入元素时,tail标记+1,比如入队三个元素,依次为A,B,C(注意是有顺序的),则当前队列存储如图

当前head为0,tail为3,接下来进行出队操作,此处将A元素出队,则head+1,此时队列的存储情况如图

  • 通过tail-head获得队列中元素的个数
  • 当tail==head时,此时队列为空
  • 当tail等于数组长度时,此时将无法出入元素,那么队列是否已经满呢?未必!因为head和tail在入队和出队操作中只增不减,因此head和tail都会最终指向队列之外的存储位置,此时虽然数组为空,但也无法将元素入队。


上溢:当队列中无法插入元素时,称之为上溢;
假上溢:在顺序队列中,数组还有空间(不一定全空)但无法入队称之为假上溢;
真上溢:如果head为0,tail指向数组之外,即数组真满了,称之为真上溢;
下溢:如果空队中执行出队操作,此时队列中无元素,称之为下溢


如何解决“假上溢”的问题呢?此时引入循环队列。出现假上溢时,此时数组还有空闲的位置,将tail从新指向数组的0索引处即可

如果继续入队E和F,则队列的存储结构如图:

通常而言,在对head和tail加1时,为了方便采用对数组长度取余操作。另外由于顺序队列存在“假上溢”的现象,所以一般用循环队列实现。

在采用循环队列实现的过程中,当队列满队时,tail等于head,而当队列空时,tail也等于head,为了区分两种状态,一般规定循环队列的长度为数组长度-1,即有一个位置不放元素,此时head==tail时为空队,而当head==(tail+1)%数组长度,说明对满。



三、队列的实现

  • 数组实现
public class ArrayQueue {
    private final Object[] queue;  //声明一个数组
    private int head;
    private int tail;
    
    /**
     * 初始化队列
     * @param capacity 队列长度
     */
    public ArrayQueue(int capacity){
        this.queue = new Object[capacity];
    }
    
    /**
     * 入队
     * @param o 入队元素
     * @return 入队成功与否
     */
    public boolean put(Object o){
        if(head == (tail+1)%queue.length){
            //说明队满
            return false;
        }
        queue[tail] = o;
        tail = (tail+1)%queue.length;  //tail标记后移一位
        return true;
    }
    
    /**
     * 返回队首元素,但不出队
     */
    public Object peak() {
        if(head==tail){
            //队空
            return null;
        }
        return queue[head];        
    }
    
    /**
     * 出队
     */
    public Object pull(){
        if(head==tail){
            return null;
        }
        Object item = queue[head];
        queue[head] = null;
        return item;
    }
    
    /**
     * 判断是否为空
     */
    public boolean isEmpty(){
        return head == tail;
    }
    
    /**
     * 判断是否为满
     */
    public boolean isFull(){
        return head == (tail+1)%queue.length;
    }
    
    /**
     * 获取队列中的元素个数
     */
    public int getsize(){
        if(tail>=head){
            return tail-head;
        }else{
            return (tail+queue.length)-head;
        }
    }    
}
  • 链表实现
public class LinkQueue<T> {

    //链的数据结构
    private class Node{
        public  T data;
        public  Node next;
        //无参构造函数
        public Node(){}

        public Node(T data,Node next){
            this.data=data;
            this.next=next;
        }
    }
    //队列头指针
    private Node front;
    //队列尾指针
    private Node rear;
    //队列长度
    private int size=0;

    public LinkQueue(){
        Node n=new Node(null,null);
        n.next=null;
        front=rear=n;
    }

    /**
     * 队列入队算法
     */
    public void enqueue(T data){
        //创建一个节点
        Node s=new Node(data,null);
        //将队尾指针指向新加入的节点,将s节点插入队尾
        rear.next=s;
        rear=s;
        size++;
    }

    /**
     * 队列出队算法
     */
    public  T dequeue(){
        if(rear==front){
            try {
                throw new Exception("堆栈为空");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }else{
            //暂存队头元素
            Node p=front.next;
            T x=p.data;
            //将队头元素所在节点摘链
            front.next=p.next;
            //判断出队列长度是否为1
            if(p.next==null)
                rear=front;
            //删除节点
            p=null;
            size--;
            return  x;
        }
    }

    /**
     * 队列长队
     */
    public int size(){
        return size;
    }

    /**
     * 判断队列是否为空
     */
    public  boolean isEmpty(){
        return  size==0;
    }
}

三、循环队列


这种头尾相接的顺序存储结构称为循环队列(circular queue)。

循环队列中需要注意的几个重要问题:

①队空的判定条件,队空的条件是front=rear;

②队满的判定条件,(rear+1)%QueueSize=front。QueueSize为队列初始空间大小。

public class CircleQueue {
	private Object[] array;
	private int capacity;//队列容量
	private int count;//队列中元素的个数
	private int front;
	private int rear;
	
	public CircleQueue(int capacity){
		this.capacity = capacity;
		array = new Object[capacity];
		count = 0;
		front = 0;
		rear = 0;
	}
	
	//入队
	public boolean append(Object data){
		boolean ret = (array != null) && (capacity > 0);
		
		if(ret){
			array[rear] = data;
			rear = (rear + 1) % capacity;
			
			count++;
			
			if(count > capacity){
				count = capacity;
			}
		}
		
		return ret;
	}
	
	//出队
	public Object retrieve(){
		Object data = null;
		
		if((array != null) && (capacity > 0) && (count > 0)){
			data = array[front];
			array[front] = null;
			front = (front + 1) % capacity;
			count--;
		}
		
		return data;
	}
	
	//获取队列中元素的个数
	public int getCount(){
		return count;
	}
	
	//获取队列的容量
	public int getCapacity(){
		return capacity;
	}
	
	//查看队头的数据,只查看,不删除。
	public Object getHead(){
		Object ret = null;
		
		if(array != null && capacity > 0 && count > 0){
			ret = array[front];
		}
		
		return ret;
	}
	
	public boolean isEmpty(){
		return count == 0;
	}
	
	//清空队列中的元素
	public void clear(){
		array = null;
		array = new Object[capacity];
		count = 0;
		front = 0;
		rear = 0;
	}
	
	public void destroy(){
		array = null;
		count = 0;
		front = 0;
		rear = 0;
	}
}

四、队列的应用场景

队列先入先出的特点,使得其应用非常广泛,比如队列作为“缓冲区”,可以解决计算机和外设速度不匹配的问题,FIFO的特点保证了数据传输的顺序;除此之外队列在后面树的层序遍历中也有应用,FIFO的特点保证了处理顺序不会出错。

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/4104.html

标签: js 队列
分享给朋友:

“先进先出的数据结构——队列” 的相关文章

Linux发行版Debian推出12.2及11.8版本,修复多个安全问题

IT之家 10 月 9 日消息,Debian 是最古老的 GNU / Linux 发行版之一,也是许多其他基于 Linux 的操作系统的基础,包括 Ubuntu、Kali、MX 和树莓派 OS 等,近日 Debian 推出了 12.2 和 11.8 版本,主要修复了多个安全问题。▲ 图源 Debia...

gitlab简单搭建与应用

一、gitlab1、简介GitLab是利用Ruby on Rails一个开源的版本管理系统,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。与Github类似,GitLab能够浏览源代码,管理缺陷和注释。可以管理团队对仓库的访问,它非常易于浏览提交过的版本并提供一个文件历...

高效使用 Vim 编辑器的 10 个技巧

在 Reverb,我们使用 MacVim 来标准化开发环境,使配对更容易,并提高效率。当我开始使用 Reverb 时,我以前从未使用过 Vim。我花了几个星期才开始感到舒服,但如果没有这样的提示,可能需要几个月的时间。这里有十个技巧可以帮助你在学习使用 Vim 时提高效率。1. 通过提高按键重复率来...

三维家-系统快捷键使用

快键件使用:通过简单的键盘+鼠标操作,快速完成搭配。1.基础快捷键1) Ctrl+V:复制选中对象第一步:鼠标左击物体,按下Ctrl+V 即可复制选中对象。2) Ctrl+G:组合多选对象第一步:按住Ctrl键多选对象--按住Ctrl+G--确定。3) Ctrl+B:解组选中对象第一步:左击选中对象...

有效地简化导航-Part 1:信息架构

「四步走」——理想的导航系统要做一个可用的导航系统,网页设计师必须按顺序回答以下4个问题:1. 如何组织内容?2. 如何解释导航的选项?3. 哪种导航菜单最适合容纳这些选项?4. 如何设计导航菜单?前两个问题关注构建和便签内容,通常称为信息架构。信息架构师通常用网站地图(site map diagr...

Vue页面传参详解

一、两种方式方法1:name跳转页面this.$router.push({name:'anotherPage',params:{id:1}})另一页面接收参数方式:this.$route.params.id示例:控制台展示:方法2:path跳转页面this.$router.push(...