跳转到内容

队列(Queue)

首发于:2020-08-03

概念

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素称为空队列。

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

顺序队列

下面是一个以数组方式实现的一个简单的顺序队列,功能是实现了但是我认为这与队列的定义还是有些区别,因为没有指针的体现:

js
const Queue = (function () {
    let items = []
    class Queue {
        constructor() {
            // 队列的数据
            items = []
        }
    
        // 向队列的尾部添加新的项
        enqueue(item) {
            items.push(item)
        }
    
        // 移除队列头部项并返回
        dequeue() {
            return items.shift()
        }
    
        // 返回队列最前面的项
        front() {
            return items[0]
        }
    
        // 返回队尾项
        rear() {
            return items[items.length - 1]
        }
    
        // 队列是否为空
        isEmpty() {
            return items.length === 0
        }
    
        // 队列长度
        size() {
            return items.length
        }
    
        // 清空队列
        clear() {
            items = []
        }
    
        // 查看队列中所有元素
        toString() {
            return items.toString()
        }
    }
    return Queue
})()

const queue = new Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.front()
queue.rear()
queue.size()
queue.isEmpty()
queue.toString()
queue.dequeue()
queue.clear()

下面是一个完全按照定义来实现的队列:

js
class Queue {
    constructor(n) {
        // 队列的数据
        this.items = []
        // 队头下标
        this.head = 0
        // 队尾下标
        this.tail = 0
        // 队列大小
        this.n = n
    }

    // 向队列的尾部添加新的项
    enqueue(item) {
        // tail 等于 size 代表队列已满
        // if (this.tail === this.n) return false
        // this.items[this.tail] = item
        // this.tail++
        // return true

        // 数据搬移进行优化,避免数组还有空闲但无法添加数据的情况
        if (this.tail === this.n) {
            // 队列到末尾了,没有空间了
            if (this.head === 0) return false // 整个队列都占满了
            // 数据搬移
            for (let i = this.head; i < this.tail; i++) {
                this.items[i - this.head] = this.items[i]
                delete this.items[i]
            }
            // 数据搬移完成之后重新更新 head 和 tail
            this.tail -= this.head
            this.head = 0   
        }
        this.items[this.tail] = item
        this.tail++
        return true
    }

    // 移除队列头部项并返回
    dequeue() {
        // head 等于 tail 代表队列为空
        if (this.head === this.tail) return null
        let res = this.items[this.head]
        delete this.items[this.head]
        this.head++
        return res
    }

    // 返回队列最前面的项
    front() {
        return this.items[this.head]
    }

    // 返回队尾项
    rear() {
        return this.items[this.tail]
    }

    // 队列是否为空
    isEmpty() {
        return this.tail === this.head
    }

    // 队列长度
    size() {
        return this.n
    }

    // 清空队列
    clear() {
        this.items = []
        this.head = 0
        this.tail = 0
    }

    // 查看队列中所有元素
    toString() {
        return this.items.toString()
    }
}

const queue = new Queue(10)
queue.enqueue(1)
queue.enqueue(2)
queue.front()
queue.rear()
queue.size()
queue.isEmpty()
queue.toString()
queue.dequeue()
queue.clear()

当front = rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队的队列元素曾经占用过得存储单元。虽然数据搬移是一种解决方案,但是入队性能会受到影响,在实际使用队列时,为了使队列空间能高效的重复使用,一般使用循环队列。

循环队列

循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现在我们把首尾相连,扳成了一个环。

循环队列就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。在循环队列结构中,当存储空间的最后一个位置已被使用而再要进入队运算时,只需要存储空间的第一个位置空闲,便可将元素加入到第一个位置,即将存储空间的第一个位置作为队尾。 循环队列可以更简单防止伪溢出的发生,但队列大小是固定的。

在循环队列中,当队列为空时,有 front = rear,而当所有队列空间全占满时,也有 front = rear。为了区别这两种情况,规定循环队列最多只能有MaxSize - 1 个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件是front = rear,而队列判满的条件是 front =(rear + 1) % MaxSize。

js
class CircularQueue {
    constructor(n) {
        // 队列的数据
        this.items = []
        // 队头下标
        this.head = 0
        // 队尾下标
        this.tail = 0
        // 队列大小
        this.n = n
    }

    // 向队列的尾部添加新的项
    enqueue(item) {
        // 队列满了
        if ((this.tail + 1) % this.n === this.head) {
            return false
        } else {
            this.items[this.tail] = item
            this.tail = (this.tail + 1) % this.n
            return true
        }
    }

    // 移除队列头部项并返回
    dequeue() {
        if (this.tail === this.head && !this.items[this.head]) {
            // 队列为空
            return false
        } else {
            let res = this.items[this.head]
            delete this.items[this.head]
            this.head = (this.head + 1) % this.n
            return res
        }
    }

    // 返回队列最前面的项
    front() {
        return this.items[this.head]
    }

    // 返回队尾项
    rear() {
        let rear = this.tail - 1
        return this.items[rear < 0 ? this.n - 1 : rear]
    }

    // 队列是否为空
    isEmpty() {
        return this.tail === this.head
    }

    // 队列长度
    size() {
        return this.n
    }

    // 清空队列
    clear() {
        this.items = []
        this.head = 0
        this.tail = 0
    }

    // 查看队列中所有元素
    toString() {
        return this.items.toString()
    }
}

const queue = new CircularQueue(10)
queue.enqueue(1)
queue.enqueue(2)
queue.front()
queue.rear()
queue.size()
queue.isEmpty()
queue.toString()
queue.dequeue()
queue.clear()

链表队列

基于链表实现的队列,我们同样需要两个指针:head 指针和 tail 指针。它们分别指向链表的第一个结点和最后一个结点。入队时,tail->next= new_node, tail = tail->next;出队时,head = head->next。

js
/**
 * 单向链表顺序队列
 */
class LinkedListQueue {
    constructor(n) {
        // 队列的数据
        this.list = new SinglyLinkedList() // 见链表单链表代码实现
        // 队头下标
        this.head = this.list.head
        // 队尾下标
        this.tail = this.list.head
        // 队列大小
        this.n = n
    }

    // 向队列的尾部添加新的项
    enqueue(item) {
        if (this.list.getLength() === this.n) return false
        this.list.append(item)
        this.tail = this.list.findLast()
        return true
    }

    // 移除队列头部项并返回
    dequeue() {
        // head 等于 tail 代表队列为空
        if (this.head === this.tail) return null
        let data = this.head.next.data
        this.head = this.head.next
        this.list.remove(data)    
        return true
    }

    // 返回队列最前面的项
    front() {
        return this.head
    }

    // 返回队尾项
    rear() {
        return this.tail
    }

    // 队列是否为空
    isEmpty() {
        return this.tail === this.head
    }

    // 队列长度
    size() {
        return this.n
    }

    // 清空队列
    clear() {
        this.list = new SinglyLinkedList()
        this.head = this.list.head
        this.tail = this.list.head
    }

    // 查看队列中所有元素
    toString() {
        return this.list.display()
    }
}

const llq = new LinkedListQueue(2)
llq.enqueue(1)
llq.enqueue(2)
llq.enqueue(3)
llq.toString()
llq.dequeue()
llq.dequeue()
llq.dequeue()
llq.toString()

京ICP备18043750号