数据结构(二) -- 队列

一,队列

与栈一样,队列也是最基本的数据结构之一。队列也是对象的一种容器,其中对象的插入和删除遵循“先进先出”(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));
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、数据结构绪论 逻辑结构与物理结构逻辑结构:集合、线性(一对一)、树(一对多)、图(多对多)物理结构:顺序存储结...
    scarqin阅读 7,336评论 0 3
  • 课程介绍 先修课:概率统计,程序设计实习,集合论与图论 后续课:算法分析与设计,编译原理,操作系统,数据库概论,人...
    ShellyWhen阅读 6,910评论 0 3
  • 3.1❶若按教科书3.1.1节中图3.1(b)所示铁道进行车厢调度(注意:两侧铁道均为单向行驶道),则请回答: (...
    云时之间阅读 6,669评论 0 3
  • 本文内容取自于小甲鱼的数据结构与算法。//www.greatytc.com/p/230e6fde9c75 ...
    阿阿阿阿毛阅读 8,044评论 0 7
  • 我喜欢你喜欢你的一切我不管我就是喜欢你我喜欢你我喜欢你!!!!!!!!!!! 不管我有多少情敌我就是喜欢你喜欢你喜...
    一只鸢阅读 891评论 0 0