队列

作者: 无止无尽 | 来源:发表于2024-07-05 18:08 被阅读0次

队列

队列是一种遵循先入先出规则的线性数据结构,顾名思义,队列模拟了排队现象,即新来的人不断加入队列末尾,而位于队列头部的人逐个离开。

队列操作

队列的常用操作

  • push(),元素入队,即将元素添加至队尾,时间复杂度O(1)
  • pop(), 队首元素出队,时间复杂度O(1)
  • peek(), 访问队首元素,时间复杂度O(1)
func main(){
    queue := list.New()

    queue.PushBack(1)
    queue.PushBack(2)
    queue.PushBack(3)

    // 访问队首元素
    front := queue.Front()
    fmt.Println(front.Value)
    // 队首元素出队
    queue.Remove(front)

    front = queue.Front()
    fmt.Println(front.Value)
    queue.Remove(front)

    front = queue.Front()
    fmt.Println(front.Value)
    queue.Remove(front)

    size := queue.Len()
    fmt.Println(size)

    isEmpty := queue.Len() == 0
    fmt.Println(isEmpty)
}

队列实现

1. 基于链表的实现

我们可以将链表的头节点和尾节点分别视为队列的首和尾,规定队尾只能添加节点,队首只能删除节点。

import "container/list"

type LinkedListQueue struct {
    *list.List
}

func NewLinkedListQueue() *LinkedListQueue {
    return &LinkedListQueue{list.New()}
}

// 入队
func (q *LinkedListQueue) Push(element interface{}) {
    q.List.PushBack(element)
}

// 出队
func (q *LinkedListQueue) Pop() interface{} {
    if q.IsEmpty() {
        return nil
    }
    e := q.List.Front()
    q.List.Remove(e)
    return e.Value
}

// 访问队首元素
func (q *LinkedListQueue) Peek() interface{} {
    if q.IsEmpty() {
        return nil
    }
    e := q.List.Front()
    return e.Value
}

// 队列长度
func (q *LinkedListQueue) Size() int {
    return q.List.Len()
}

func (q *LinkedListQueue) IsEmpty() bool {
    return q.List.Len() == 0
}

2. 基于数组的实现

在数组中删除首元素的时间复杂度为O(n),这会导致出队操作效率低。我们可以采用以下技巧来避免这个问题。
使用一个变量front指向队首元素的索引,并维护一个size变量记录队列的长度。定义 rear = front + size, 这个公式计算出的rear指向队尾元素的下一个位置。至此,队首元素和队尾元素的位置我们都能在O(1)时间复杂度内确认。
基于此设计,数组中包含元素的区间为[front, rear - 1],

  • 入队操作:将输入元素赋值给索引rear索引处,并将长度size增加1.
  • 出队操作:将front加1,并将size减1
    这样做的好处就是,出队时,不需要删除队首元素,只需改变队首的指针,而原队首的元素值会被后续入队的元素覆盖。可以想象成一个环形数组首尾相连。
/* 基于环形数组实现的队列 */
type arrayQueue struct {
    nums        []int // 用于存储队列元素的数组
    front       int   // 队首指针,指向队首元素
    queSize     int   // 队列长度
    queCapacity int   // 队列容量(即最大容纳元素数量)
}

/* 初始化队列 */
func newArrayQueue(queCapacity int) *arrayQueue {
    return &arrayQueue{
        nums:        make([]int, queCapacity),
        queCapacity: queCapacity,
        front:       0,
        queSize:     0,
    }
}

/* 获取队列的长度 */
func (q *arrayQueue) size() int {
    return q.queSize
}

/* 判断队列是否为空 */
func (q *arrayQueue) isEmpty() bool {
    return q.queSize == 0
}

/* 入队 */
func (q *arrayQueue) push(num int) {
    // 当 rear == queCapacity 表示队列已满
    if q.queSize == q.queCapacity {
        return
    }
    // 计算队尾指针,指向队尾索引 + 1
    // 通过取余操作实现 rear 越过数组尾部后回到头部
    rear := (q.front + q.queSize) % q.queCapacity
    // 将 num 添加至队尾
    q.nums[rear] = num
    q.queSize++
}

/* 出队 */
func (q *arrayQueue) pop() any {
    num := q.peek()
    if num == nil {
        return nil
    }

    // 队首指针向后移动一位,若越过尾部,则返回到数组头部
    q.front = (q.front + 1) % q.queCapacity
    q.queSize--
    return num
}

/* 访问队首元素 */
func (q *arrayQueue) peek() any {
    if q.isEmpty() {
        return nil
    }
    return q.nums[q.front]
}

/* 获取 Slice 用于打印 */
func (q *arrayQueue) toSlice() []int {
    rear := (q.front + q.queSize)
    if rear >= q.queCapacity {
        rear %= q.queCapacity
        return append(q.nums[q.front:], q.nums[:rear]...)
    }
    return q.nums[q.front:rear]
}

以上队列实现仍然有局限性,队列长度固定;为此我们需要使用动态数组来解决这个问题。

/* 初始化队列 */
func newArrayQueue() *arrayQueue {
    return &arrayQueue{
        queCapacity: 0,
        front:       0,
        queSize:     0,
    }
}

/* 入队 */
func (q *arrayQueue) push(num int) {
    // 队列为空,则扩容为1
    if q.queCapacity == 0 {
        q.nums = make([]int, 1)
        q.queCapacity = 1
    }

    // 当 rear == queCapacity 表示队列已满,此时需要扩容
    if q.queSize == q.queCapacity {
        // 增加1倍容量
        enlargeNum := make([]int, q.queCapacity)
        // 添加到原队列
        q.nums = append(q.nums, enlargeNum...)
        q.queCapacity = 2 * q.queCapacity

    }
    // 计算队尾指针,指向队尾索引 + 1
    rear := (q.front + q.queSize)
    // 将 num 添加至队尾
    q.nums[rear] = num
    q.queSize++
}

/* 出队 */
func (q *arrayQueue) pop() any {
    num := q.peek()
    if num == nil {
        return nil
    }

    q.front = (q.front + 1)
    q.queSize--
    return num
}

在此实现方式中,队列元素不会超出,也就不需要通过求余重置队首的索引,但是元素出栈时,并不是真正从数组移除,只是改变了队首指针的位置,所以必定会造成空间浪费,但是时间复杂度仍然是O(1)。
对比两种方式,各有利弊,当然这里只是模拟实现,真正实现可能还需要考虑更多的问题。

队列应用

  • 淘宝订单。购物者下单后,订单将加入队列中,系统随后会根据顺序处理队列中的订单。在双十一期间,短时间内会产生海量订单,高并发成为工程师们需要重点攻克的问题。
  • 各类待办事项。任何需要实现“先来后到”功能的场景,例如打印机的任务队列、餐厅的出餐队列等,队列在这些场景中可以有效地维护处理顺序。

相关文章

  • 队列

    队列特性 对比队列和栈 基于数组的队列 对比队列学习循环队列 循环队列难点 阻塞队列 并发队列 应用:线程池中拒绝...

  • 队列

    文章结构 什么是队列 实现队列顺序队列链式队列循环队列 Java中的队列 1. 什么是队列 队列也是一种操作受限的...

  • iOS底层-- GCD源码分析(1)-- dispatch_qu

    手动目录认识队列队列的结构队列的产生主队列全局队列创建的队列管理队列 代码版本dispatch version :...

  • 队列,异步,同步,线程通俗理解

    一、队列 串行队列 并行队列 主队列(只在主线程执行的串行队列) 全局队列(系统的并行队列) 二、 任务(是否具有...

  • GCD基础总结一

    上代码~ 同步串行队列 同步并行队列 异步串行队列 异步并行队列 主队列同步 会卡住 主队列异步

  • OC多线程

    队列创建 线程与队列 队列线程间通信 队列组

  • GCD

    获得主队列 获得全局队列 串行队列 异步队列 同步队列 阻隔队列 (像栅栏一样 ) 例如 A -->栅栏 --...

  • 数据结构第三篇 队列

    队列的特性 前进先出。 我们来大致描述下进出队列的情况。 进队列 1 进队列现在队列是 12 进队列现在队列是 1...

  • 利用链表实现队列

    队列成员变量: 队列长度 队列头节点 队列尾节点队列方法: 队列包含元素个数 队列是否为空 进队操作 出队操作 d...

  • Git 常用操作命令(持续更新)

    当前更新到stash队列 查看stash队列 清空队列 删除某个队列

网友评论

      本文标题:队列

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