大话数据结构 - 队列

代码GitHub地址


队列

  • 队列和栈一样是特殊的线性表。区别只是它能尾进头出而已
  • 学习队列需要清楚的认识到frontrear两指针什么情况下分别变动。

队列也分成两种:

  • 静态队列(数组实现)
  • 动态队列(链表实现)

需要注意一下:静态队列会遇到 “假溢出” 情况,即:当我们为了避免静态队列在进行元素删除操作性能差的时候(所有队列元素向前移位,来补数组删除元素后空出来的位置),我们通过移动front指针移动来定位新的队头元素,而不是先前的移动剩余节点来确定数组下标为0的位置为队头。直到front=rear的时候认为该队列为空。但是这种优化方式会出现假溢出的现象,即我们front一直向后移,而队尾rear已经到数组尾了。当再插入元素的时候该存到哪里呢,此时就会出现数组越界异常。而此时我们的数组从下标为0的地方到front的位置却是没有任何元素的。


解决办法:循环队列

解决思路:rear原先的处理方式是指向尾节点的下一位置。当出现上述情况的时候rear指向数组下标为0的位置。形成循环队列,这样rear就不会指向数组arrayLength位置外的位置了。


新问题:当rear=front时,可能有队满和队空两种情况。rear一直加元素向后走顶到front处(队满),和front一直删顶到rear处(队空)。

解决办法:添加元素的时候rear最多添加元素到front - 1的索引处。这样frontrear就不可能重合了。我们要熟记这个关系。front = rear即空队,满队即front + 1 = rear。后续有很多操作会用到

队列常用三个判断因式

  • 队满判断因式:(rear + 1) % QueueSize == front
  • 队空的判断因式:rear == front
  • 通用队长计算因式:(QueueSize - front + rear) % QueueSize

联想图进行理解很好理解。

静态队列

循环队列的实现由于是基于数组实现,所以我们没有链表中尾节点next指针指向头节点的操作。但是可以通过rear字段来记录尾节点下标来达到循环队列的目的。

数组实现的方式在进行元素删除时的操作都是不对元素处理,通过移动“记录变量”的指向来无视该被删除的值。即便他在数组空间中任然真实存在,但是已然不会被该数据结构的索引方式所索引到。

  • 需要注意的是静态队列我们一般都会做成循环队列,这样才能更好地使用内存空间规避掉“假溢出”的现象

静态队列的操作

队列

public class Queue {

    private static final int QUEUE_MAX_SIZE = 100;

    /**
     * 队头指针
     */
    public int front = 0;
    /**
     * 队尾指针
     */
    public int rear = 0;
    /**
     * 队列元素容器
     */
    public int[] queueData = new int[QUEUE_MAX_SIZE];
}

返回循环队列的队长

/**
 * 返回循环队列的队长
 *
 * @param queue
 * @return
 */
public static int queueLength(Queue queue) {
    // |   | rear  |   |   | front  |   |
    return (QUEUE_MAX_SIZE - queue.front + queue.rear) % QUEUE_MAX_SIZE;
}

只要记住这个逻辑图或按公式来,根据% QUEUE_MAX_SIZE来适配全部情况即可。

循环队列入队

/**
 * 循环队列入队
 *
 * @param queue
 * @param value
 * @return
 */
public static int insertQueue(Queue queue, int value) {
    if ((queue.rear + 1) % QUEUE_MAX_SIZE == queue.front) {
        return 0;
    }
    queue.queueData[queue.rear] = value;
     /*这里需要注意`queue.rear++`后要对总数QUEUE_MAX_SIZE取余
      这样才满足了循环队列的情况 否则可能会出现数组越界的异常*/
    queue.rear = queue.rear++ % QUEUE_MAX_SIZE;
    return 1;
}

这里也是根据那个逻辑图想逻辑就可,也可以直接套公式。

循环队列出队

/**
 * 循环队列出队
 *
 * @param queue
 * @return
 */
public static int removeQueue(Queue queue) {
    if (queue.front == queue.rear) {
        return 0;
    }
    // 不处理,移指针来规避
    queue.front = queue.front++ % QUEUE_MAX_SIZE;
    return 1;
}

需要特别提醒的一点就是我们在任何数组实现的数据结构中,在底层删除数据的时候的处理都是不做处理,只是移动“标识指针”,进而达到审查不到该元素的目的。其实最好的处理方式当然是根据数组的元素类型来直接置空或者为0来释放对象。

遍历循环队列

/**
 * 遍历循环队列
 *
 * @param queue
 * @return
 */
public static void traverse(Queue_ queue) {
    int index = queue.front;
    // 这里需要注意 rear 指向的是队尾节点的下一位置,而非队尾的位置,所以 rear 处不输出
    while (index != queue.rear) {
        int value = queue.queueData[index];
        System.out.println(value);

        index = index++ % queue.queueData.length;
    }
}

这里需要注意rear指向的是队尾节点的下一位置,而非队尾的位置,所以 rear处不输出

判断该队列是否为空

/**
 * 判断该队列是否为空
 *
 * @param queue
 * @return
 */
public static int isEmpty(Queue_ queue) {
    if (queue.front == queue.rear) {
        return 1;
    }
    return 0;
}

只要清楚我们已经让rear指向了队尾后一位的位置,以及当front=rear时队为空即可。

链式队列

链队列根本上还是链表。只是玩的是“节点标识“的把戏,并且被规定节点只能从队尾进队首出。我们凭借标识节点来确定这个数据结构

链式队列的操作

队列

public class Queue_ {

    /**
     * 队头指针
     */
    public Node front;
    /**
     * 队尾指针
     */
    public Node rear;

}

链队列元素入队

/**
 * 链队列元素入队
 *
 * @param queue
 * @param value
 * @return
 */
public static int insert(Queue_ queue, int value) {
    Node node = new Node(value, null);
    queue.rear.nextNode = node;
    queue.rear = node;
    return 1;
}

链队列不存在数据越界的情况,给rear尾节点加值即可。然后记得再移动尾节点的指针

链队列的出队

/**
 * 链队列的出队
 *
 * @param queue
 * @return
 */
public static int remove(Queue_ queue) {
    // 空队
    if (queue.front == queue.rear) {
        return 0;
    }
    Node firstNode = queue.front.nextNode;
    // 只剩一个节点的时候 
    if (firstNode.nextNode == null && queue.rear == firstNode) {
        queue.front.nextNode = null;
        // 队尾指针指向虚拟头结点
        queue.rear = queue.front;
    }
    queue.front.nextNode = firstNode.nextNode;
    return 1;
}

删除节点的时候需要判断一下,边界条件。考虑当该队列为空队的情况,以及队中只剩一个元素时rearfront的指向。因为front一般指向头结点而非队头节点,所以在链队列中front不会动,只是在删除元素时不断的变更front.nextNode属性,而不会去动rear。只剩一个元素的特殊情况时,我们就需要把rear考虑进来了。由于rear指向的是队尾元素,删除掉最后一个元素之后就不存在了,所以它只能到front队头的位置。而front.nextNode = null

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容