• 数据结构(六)-----队列


    如何理解“队列”?

    队列这个概念非常好理解。你可以把它想象成排队买票,先来的先买,后来的人只能站末尾,不允许插队。先进者先出,这就是典型的“队列”。我们知道,栈只支持两个基本操作:入栈 push()和出栈 pop()。队列跟栈非常相似,支持的操作也很有限,最基本的
    操作也是两个:入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头部取一个元素。

    顺序队列和链式队列

    跟栈一样,队列可以用数组来实现,也可以用链表来实现。用数组实现的栈叫作顺序栈,用链表实现的栈叫作链式栈。同样,用数组实现的队列叫作顺序队列,用链表实现的队列叫作链式队列。

    数组实现

    代码示例:

    // 用数组实现的队列
    public class ArrayQueue {
        // 数组:items,数组大小:n
        private String[] items;
        private int n = 0;
        // head 表示队头下标,tail 表示队尾下标
        private int head = 0;
        private int tail = 0;
    
        // 申请一个大小为 capacity 的数组
        public ArrayQueue(int capacity) {
            items = new String[capacity];
            n = capacity;
        }
    
        // 入队
        public boolean enqueue(String item) {
            // 如果 tail == n 表示队列已经满了
            if (tail == n) return false;
            items[tail] = item;
            ++tail;
            return true;
        }
    
        // 出队
        public String dequeue() {
            // 如果 head == tail 表示队列为空
            if (head == tail) return null;
            // 为了让其他语言的同学看的更加明确,把 -- 操作放到单独一行来写了
            String ret = items[head];
            ++head;
            return ret;
        }
    }
    当 a、b、c、d 依次入队之后,队列中的 head 指针指向下标为 0 的位置,tail 指针指向下标为 4 的位置。
    当我们调用两次出队操作之后,队列中 head 指针指向下标为 2 的位置,tail 指针仍然指向下标为 4 的位置。

    随着不停地进行入队、出队操作,head 和 tail 都会持续往后移动。当tail 移动到最右边,即使数组中还有空闲空间,也无法继续往队列中添加数据了。这个问题该如何解决呢?

    那只能通过数据搬运来解决了,保持出队逻辑不变,修改入队逻辑即可。

    // 入队操作,将 item 放入队尾
    public boolean enqueue(String item) {
        // tail == n 表示队列末尾没有空间了
        if (tail == n) {
            // tail ==n && head==0,表示整个队列都占满了
            if (head == 0) return false;
                // 数据搬移
                for (int i = head; i < tail; ++i) {
                    items[i-head] = items[i];
                }
            // 搬移完之后重新更新 head 和 tail
            tail -= head;
            head = 0;
        }
    
        items[tail] = item;
        ++tail;
        return true;
    }
    从代码中我们看到,当队列的 tail 指针移动到数组的最右边后,如果有新的数据入队,我们可以将 head 到 tail 之间的数据,整体搬移到数组中 0 到 tail-head 的位置。

    链表实现

    思路:

    循环队列

    我们刚才用数组来实现队列的时候,在 tail==n 时,会有数据搬移操作,这样入队操作性能就会受到影响。那有没有办法能够避免数据搬移呢?我们来看看循环队列的解决思路。循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现
    在我们把首尾相连,扳成了一个环。

    我们可以看到,图中这个队列的大小为 8,当前 head=4,tail=7。当有一个新的元素 a 入队时,我们放入下标为 7 的位置。但这个时候,我们并不把 tail 更新为 8,而是将其在环中后移一位,到下标为 0 的位置。当再有一个元素 b 入队时,我们将 b 放入下标
    为 0 的位置,然后 tail 加 1 更新为 1。所以,在 a,b 依次入队之后,循环队列中的元素就变成了下面的样子:

    通过这样的方法,我们成功避免了数据搬移操作。看起来不难理解,但是循环队列的代码实现难度要比前面讲的非循环队列难多了。要想写出没有 bug 的循环队列的实现代码,我个人觉得,最关键的是,确定好队空和队满的判定条件。在用数组实现的非循环
    队列中,队满的判断条件是 tail == n,队空的判断条件是 head ==tail。那针对循环队列,如何判断队空和队满呢?队列为空的判断条件仍然是 head == tail。
    tail=3,head=4,n=8,所以总结一下规律就是:(3+1)%8=4。多画几张队满的图,你就会发现,当队满时,(tail+1)%n=head。你有没有发现,当队列满时,图中的 tail 指向的位置实际上是没有存储数据的。所以,循环队列会浪费一个数组的存储空间。
    解释:head、tail的位置不会大于整个环的长,比如环长为8,那么head、tail的下标范围为[0,8),因此环满的时候,也就是tail为head的前一位,因此head=(tail+1)%n。
    public class CircularQueue {
        // 数组:items,数组大小:n 
        private String[] items;
        private int n = 0;
        // head 表示队头下标,tail 表示队尾下标
        private int head = 0;
        private int tail = 0;
        // 申请一个大小为 capacity 的数组
        public CircularQueue(int capacity) {
            items = new String[capacity];
            n = capacity;
        }
    
        // 入队
        public boolean enqueue(String item) {
            // 队列满了
            if ((tail + 1) % n == head) return false;
            items[tail] = item;
            tail = (tail + 1) % n;
            return true;
        }
    
        // 出队
        public String dequeue() {
            // 如果 head == tail 表示队列为空
            if (head == tail) return null;
            String ret = items[head];
            head = (head + 1) % n;
            return ret;
        }
    }
  • 相关阅读:
    Leetcode86.分隔链表
    Leetcode39.组合总和
    Leetcode31.下一个排列
    剑指Offer35.复杂链表复制
    剑指Offer14-I.剪绳子
    剑指Offer38.字符串的排序
    Leetcode29.两数相除
    232. Implement Queue using Stacks
    程序员跳槽指南
    226. Invert Binary Tree
  • 原文地址:https://www.cnblogs.com/alimayun/p/13187012.html
Copyright © 2020-2023  润新知