美文网首页
数据结构(二) -- 队列

数据结构(二) -- 队列

作者: 峰峰小 | 来源:发表于2016-09-26 02:51 被阅读181次

一,队列

与栈一样,队列也是最基本的数据结构之一。队列也是对象的一种容器,其中对象的插入和删除遵循“先进先出”(First-In-First-Out, FIFO)的原则--也就是说,每次删除的只能是最先插入的对象。因此,我们可以想象成将对象追加在队列的后端,而从其前端摘除对象。就这一性质而言,队列与栈堪称“孪生兄弟”。比如,在银行等待接受服务时,顾客们就会排成一个队列--最先到达者优先得到服务。再如,用球桶来乘放羽毛球时,所有的球也排成一个队列--你总是从一端将球取出,而从另一端把球放入。



二,队列ADT(AbstractDataType)

队列的抽象数据类型就是一个容器,其中的对象排成一个序列,我们只能访问和取出排在最前端(Front)的对象,只能在队列的尾部(Rear)插入新对象。正是按照这一规则,才能保证最先被插入的对象首先被删除(FIFO)。

队列ADT 首先支持下面的两个基本方法:

操作方法 功能描述
enqueue(x) 将元素x 加到队列末端
输入:一个对象
输出:无
dequeue() 若队列非空,则将队首元素移除,并将其返回否则,报错
输入:无
输出:对象

此外,与栈ADT 类似地,队列ADT 还支持如下的方法:

操作方法 功能描述
getSize() 返回队列中当前包含的元素数目
输入:无
输出:非负整数
isEmpty() 检查队列是否为空
输入:无
输出:布尔标志
front() 若队列非空,则返回队首元素(但并不移除)否则,报错
输入:无
输出:队头对象

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


三,基于定长循环数组的队列的实现

定义一个Queue 接口

/*
* 队列接口
*/
package dsa;
public interface Queue {
  public int getSize();//返回队列中元素数目
  public boolean isEmpty();//判断队列是否为空
  public Object front()throws ExceptionQueueEmpty;//取队首元素(但不删除)
  public void enqueue (Object obj)throws ExceptionQueueFull;//入队
  public Object dequeue()throws ExceptionQueueEmpty;//出队
  public void Traversal();//遍历
}

定义用到的两个异常:
一个Queue空异常

package dsa;
    public class ExceptionQueueEmpty extends RuntimeException {
    public ExceptionQueueEmpty (String err) {
        super(err);
    }
}

一个Queue满异常

package dsa;
    public class ExceptionQueueFull extends RuntimeException {
    public ExceptionQueueFull (String err) {
        super(err);
    }
}

循环数组
为了避免数组的整体移动,可以引入如下两个变量f 和r:

  • f:始终等于Q 的首元素在数组中的下标,即指向下次出队元素的位置
  • r:始终等于Q 的末元素的下标加一,即指向下次入队元素的位置

一开始,f = r = 0,此时队空。每次有对象入队时,将其存放于Q[r],然后r 加一,以指向下一单元。对称地,每次有对象出队之后,也将f 加一,指向新的队首元素。这样,对front()、enqueue()和dequeue()方法的每一次调用都只需常数时间。

然而,这还不够。细心的读者或许已经注意到,按照上述约定,在队列的生命期内,f 和r 始终在单调增加。因此,若队列数组的容量为N,则在经过N 次入队操作后,r 所指向的单元必然超出数组的范围;在经过N 次出队操作后,f 所指向的单元也会出现类似的问题。

解决上述问题的一种简便方法,就是在每次f 或r 加一后,都要以数组的长度做取模运算,以保证其所指单元的合法性。就其效果而言,这就相当于把数组的头和尾相联,构成一个环状结构。

具体的实现:

public class Queue_Array implements Queue {
    public static final int CAPACITY = 1000;// 数组的默认容量
    protected int capacity;// 数组的实际容量
    protected Object[] Q;// 对象数组
    protected int f = 0;// 队首元素的位置
    protected int r = 0;// 队尾元素的位置

    // 构造方法(空队列)

    public Queue_Array() {
        this(CAPACITY);
    }

    // 按指定容量创建对象
    public Queue_Array(int cap) {
        capacity = cap;
        Q = new Object[capacity];
    }

    // 查询当前队列的规模
    public int getSize() {
        return (capacity - f + r) % capacity;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return (f == r);
    }

    // 入队
    public void enqueue(Object obj) throws ExceptionQueueFull {
        if (getSize() == capacity - 1)
            throw new ExceptionQueueFull("Queue overflow.");
        Q[r] = obj;
        r = (r + 1) % capacity;
    }

    // 出队
    public Object dequeue() {
        Object elem;
        if (isEmpty())
            throw new ExceptionQueueEmpty("意外:队列空");
        elem = Q[f];
        Q[f] = null;
        f = (f + 1) % capacity;
        return elem;
    }

    // 取(并不删除)队首元素
    public Object front() throws ExceptionQueueEmpty {
        if (isEmpty())
            throw new ExceptionQueueEmpty("意外:队列空");
        return Q[f];

    }

    // 遍历(不属于ADT)
    public void Traversal() {
        for (int i = f; i < r; i++)
            System.out.print(Q[i] + " ");
        System.out.println();
    }
}

四,队列应用实例

  • ** 循环分配器 **
    队列结构很适于用来实现循环分配器:按照环形次序反复循环,为共享某一资源的一群客户(比如共享一个CPU 的多个应用程序)做资源分配。
算法:RoundRobin
{
    e = Q.dequeue();
    Serve(e);
    Q.enqueue(e);
}
  • ** Josephus 环 (约瑟夫环)**
    孩提时的你是否玩过“烫手山芋”游戏:一群小孩围成一圈,有一个刚出锅的山芋在他们之间传递。其中一个孩子负责数数,每数一次,拿着山芋的孩子就把山芋转交给右边的邻居。一旦数到某个特定的数,拿着山芋的孩子就必须退出,然后重新数数。如此不断,最后剩下的那个孩子就是幸运者。
    通常,数数的规则总是从1 开始,数到k 时让拿着山芋的孩子出列,然后重新从1 开始。Josephus问题可以表述为:n 个孩子玩这个游戏,最后的幸运者是谁?
    为了解答这个问题,我们可以利用队列结构来表示围成一圈的n个孩子。一开始,假定对应于队列首节点的那个孩子拿着山芋。然后,按照游戏的规则,把“土豆”向后传递到第k个孩子(交替进行k次dequeue()和k次enqueue()操作),并让她出队(dequeue())。如此不断迭代,直到队长
    (getSize())为1。
/*
 * Josephus环
 */
import dsa.*;
import java.io.*;

//模拟Josephus环
public class Josephus {

    // 利用队列结构模拟Josophus环
    public static Object Josephus(Queue Q, int k) {
        if (Q.isEmpty())
            return null;
        while (Q.getSize() > 1) {// 不断迭代
            Q.Traversal();// 显示当前的环
            for (int i = 0; i < k; i++)
                // 将山芋向前传递k次
                Q.enqueue(Q.dequeue());
            Object e = Q.dequeue();// 拿着山芋的孩子退出
            System.out.println("\n\t" + e + "退出");
        }
        return Q.dequeue();// 最后剩下的那个孩子
    }

    // 将一组对象组织为一个队列
    public static Queue buildQueue(Object a[]) {
        Queue Q = new Queue_Array();
        for (int i = 0; i < a.length; i++)
            Q.enqueue(a[i]);
        return Q;
    }

    // 测试用main方法
    public static void main(String[] args) {
        String[] kid = { "Alice", "Bob", "Cindy", "Doug", "Ed", "Fred", "Gene",
                "Hope", "Irene", "Jack", "Kim", "Lance", "Mike", "Nancy",
                "Ollie" };
        System.out.println("最终的幸运者是" + Josephus(buildQueue(kid), 5));
    }
}

相关文章

  • 看图说话数据结构之二项队列(优先队列)——原理解析

    数据结构之二叉堆(优先队列)——原理解析,数据结构之二叉堆(优先队列)——java实现,数据结构之左式堆(优先队列...

  • 数据结构 - 概要

    数组 链表 堆/栈/队列 树 数据结构 - 二叉树数据结构 - 二叉查找树数据结构 - 平衡二叉树数据结构 - A...

  • 2019-02-23 普林斯顿大学 数据结构课程笔记

    一、 数据结构:基本数据结构:栈、队列、背包、优先队列 排序:排序、归并排序、堆排序、基数排序 查找:二叉查找树、...

  • 数据结构

    数据结构 数据结构概念 顺序表 链表 队列 栈 二叉树 常用排序算法

  • 数据结构导读目录

    数据结构(1)-常见数据结构数据结构(2)-栈和队列和Hash表数据结构(3)-树和二叉树的遍历数据结构(4)-二...

  • skynet 源码阅读笔记 —— 消息调度机制

    基本数据结构之消息队列 skynet 采用了二级消息队列模式,其中顶层消息队列为 global_queue,而底层...

  • java中的队列

    一、队列是什么 队列是一种先进先出的数据结构。 二、队列的接口定义 方法名称作用队列满boolean add(E ...

  • Swing事件分发线程

    事件队列:在计算机数据结构中,队列是一个特殊的数据结构。其一、它是线性的;其二、元素是先进先出的,也就是说进入队列...

  • 二叉树 队列 数组 层次遍历

    bfs 和 队列 学过数据结构的都知道,二叉树的层次遍历。 层次遍历利用的数据结构是队列。 那么, 思考一下 为什...

  • 学习JavaScript数据结构与算法(第2版)

    二维和多维数组 栈数据结构 队列数据结构(排队) 链表数据结构 双向链表 集合 字典和散列表 散列表 树 二叉树 ...

网友评论

      本文标题:数据结构(二) -- 队列

      本文链接:https://www.haomeiwen.com/subject/itooettx.html