队列一站学习|青训营笔记

152 阅读5分钟

队列

三、队列 (顺序、循环、链式)

队列(queue)是一种先进先出的数据结构,是一个封装了动态大小数组的顺序容器(底层动态数组).

queue := []int{} // 声明一个空的int类型切片作为队列

queue = append(queue, 1) // 入队操作
x := queue[0] // 取队首元素
queue = queue[1:] // 出队操作

len(queue) == 0 //检查队列为空

队列的基本操作总结

		Size() (num uint64)     //返回该队列中元素的使用空间大小
    Clear()                 //清空该队列
    Empty() (b bool)        //判断该队列是否为空
    Push(e interface{})     //将元素e添加到该队列末尾
    Pop() (e interface{})   //将该队列首元素弹出并返回
    Front() (e interface{}) //获取该队列首元素
    Back() (e interface{})  //获取该队列尾元素

以下是三种队列的数据结构和基本操作实现⬇️

顺序队列(队列元素大小固定可采取)

  • 基于数组实现。顺序队列因为在队头删除元素时要移动其他元素,所以相对其他队列实现方式,比较低效。

                                                  顺序队列结构体⬇️
    
type Queue struct {
    elements []interface{} //元素数组
    front    int //队头指针
    rear     int //队尾指针
    size     int //队列元素数量
    capacity int //队列容量
}
  • 顺序队列的底层数据结构实现

    // 返回该队列中元素的使用空间大小
    func (q *Queue) Size() int{
        return q.size
    }
    
    // 清空该队列
    func (q *Queue) Clear() {
        q.elements = make([]interface{}, 0)
        q.front = 0
        q.rear = 0
        q.size = 0
    }
    
    // 判断该队列是否为空
    func (q *Queue) Empty() bool {
        return q.size == 0
    }
    
    // 将元素e添加到该队列末尾
    func (q *Queue) Push(e interface{}) {
        if q.size == q.capacity {
            q.elements = append(q.elements, e)
            q.rear = q.size
            q.capacity++
        } else {
            q.elements[q.rear] = e
            q.rear++
        }
        q.size++
    }
    
    // 将该队列首元素弹出并返回
    func (q *Queue) Pop() interface{} {
        if q.size == 0 {
            return nil
        }
        ret := q.elements[q.front]
        q.front++
        q.size--
        return ret
    }
    
    // 获取该队列首元素
    func (q *Queue) Front() interface{} {
        if q.size == 0 {
            return nil
        }
        return q.elements[q.front]
    }
    
    // 获取该队列尾元素
    func (q *Queue) Back() interface{} {
        if q.size == 0 {
            return nil
        }
        return q.elements[q.rear-1]
    }
    

顺序队列基本的操作


//顺序队列基本的操作

func main() {
    queue := Queue{
        elements: make([]interface{}, 0),
        front:    0,
        rear:     0,
        size:     0,
        capacity: 0,
    }

    // push操作
    queue.Push(1)
    queue.Push(2)
    queue.Push(3)
//当前队列元素: [1 2 3]

    // size操作
    fmt.Println("当前队列元素数量:", queue.Size())
//当前队列元素数量: 3

    // clear操作
    queue.Clear()
    fmt.Println("清空队列后,队列元素数量:", queue.Size())
//清空队列后,队列元素数量: 0

    // empty操作
    fmt.Println("队列是否为空:", queue.Empty())
//队列是否为空: true

    // push操作
    queue.Push(4)
    queue.Push(5)
    queue.Push(6)
    fmt.Println("当前队列元素:", queue.elements)
////当前队列元素: [4 5 6]

    // front操作
    fmt.Println("队列首元素:", queue.Front())
//队列首元素:4

    // back操作
    fmt.Println("队列尾元素:", queue.Back())
//队列尾元素:6

    // pop操作
    queue.Pop()
    fmt.Println("pop操作后,队列元素:", queue.elements)
}
//pop操作后,队列元素:5 6

循环队列

循环队列是一种更高效的队列实现方式,它同样基于数组实现。循环队列不用每次移动元素,而是通过队列头尾两端的指针来控制。当队列满时,可以将队头指针重置为 0,从而形成循环队列。

                                                 循环队列结构体⬇️
type Queue struct {
    elements []interface{} //元素数组
    front    int //队头指针
    rear     int //队尾指针
    size     int //队列元素数量
    capacity int //队列容量
}
  • 循环队列的底层数据结构实现

    type Queue struct {
        elements []interface{} //元素数组
        front    int //队头指针
        rear     int //队尾指针
        size     int //队列元素数量
        capacity int //队列容量
    }
    
    // 返回该队列中元素的使用空间大小
    func (q *Queue) Size() int{
        return q.size
    }
    
    // 清空该队列
    func (q *Queue) Clear() {
        q.elements = make([]interface{}, q.capacity)
        q.front = 0
        q.rear = 0
        q.size = 0
    }
    
    // 判断该队列是否为空
    func (q *Queue) Empty() bool {
        return q.size == 0
    }
    
    // 将元素e添加到该队列末尾
    func (q *Queue) Push(e interface{}) {
        if q.size == q.capacity {
            newElements := make([]interface{}, q.capacity*2)
            if q.front <= q.rear {
                copy(newElements, q.elements[q.front:q.rear])
            } else {
                copy(newElements, q.elements[q.front:])
                copy(newElements[q.capacity-q.front:], q.elements[:q.rear])
            }
            q.elements = newElements
            q.rear = q.size
            q.capacity *= 2
        }
        q.elements[q.rear%q.capacity] = e
        q.rear++
        q.size++
    }
    
    // 将该队列首元素弹出并返回
    func (q *Queue) Pop() interface{} {
        if q.size == 0 {
            return nil
        }
        ret := q.elements[q.front%q.capacity]
        q.front++
        q.size--
        return ret
    }
    
    // 获取该队列首元素
    func (q *Queue) Front() interface{} {
        if q.size == 0 {
            return nil
        }
        return q.elements[q.front%q.capacity]
    }
    
    // 获取该队列尾元素
    func (q *Queue) Back() interface{} {
        if q.size == 0 {
            return nil
        }
        return q.elements[(q.rear-1)%q.capacity]
    }
    
//循环队列与顺序队列操作一致
//循环队列注意:当切片元素数量达到容量极限时,切片会自动扩容,此时队列的front和rear指针需要进行调整。

链式队列(空间利用率高)

链式队列是基于链表实现的,不需要预先指定队列容量,队列元素可以动态增长。链式队列不需要移动元素,因为链表的特性,它的入队和出队都是 O(1) 的时间复杂度。

                                                  链式队列结构体⬇️
type Node struct {
    data interface{}
    next *Node
}

type LinkedQueue struct {
    front *Node
    rear  *Node
    size  int
}
  • 循环队列的底层数据结构实现

    // 返回该队列中元素的使用空间大小
    func (q *LinkedQueue) Size() int{
        return q.size
    }
    
    // 清空该队列
    func (q *LinkedQueue) Clear() {
        q.front = nil
        q.rear = nil
        q.size = 0
    }
    
    // 判断该队列是否为空
    func (q *LinkedQueue) Empty() bool {
        return q.size == 0
    }
    
    // 将元素e添加到该队列末尾
    func (q *LinkedQueue) Push(e interface{}) {
        node := &Node{
            data: e,
            next: nil,
        }
        if q.rear == nil {
            q.front = node
            q.rear = node
        } else {
            q.rear.next = node
            q.rear = node
        }
        q.size++
    }
    
    // 将该队列首元素弹出并返回
    func (q *LinkedQueue) Pop() interface{} {
        if q.size == 0 {
            return nil
        }
        ret := q.front.data
        q.front = q.front.next
        q.size--
        return ret
    }
    
////链式队列的基本操作与顺序队列、循环队列一致.

func main() {
    queue := LinkedQueue{
        front: nil,
        rear:  nil,
        size:  0,
    }
}

如果我们使用包pkg来导入队列的相关操作,那么有如下

在Go语言中,可以使用切片(slice)或者使用标准库中的container/list包来实现队列。下面我们将分别介绍这两种方式的实现方法。

1、使用切片实现队列

使用切片实现队列相对简单,可以通过追加(append)和切片操作来实现入队和出队操作。下面是一个使用切片实现队列的示例代码:

package main

import "fmt"

type Queue struct {
    elements []int
}

func (q *Queue) Enqueue(element int) {
    q.elements = append(q.elements, element)
}

func (q *Queue) Dequeue() (int, bool) {
    if len(q.elements) == 0 {
        return 0, false
    }
    element := q.elements[0]
    q.elements = q.elements[1:]
    return element, true
}

func main() {
    queue := Queue{}
    queue.Enqueue(1)
    queue.Enqueue(2)
    queue.Enqueue(3)

    for {
        element, ok := queue.Dequeue()
        if !ok {
            break
        }
        fmt.Println(element)
    }
}

在上述代码中,我们定义了一个Queue结构体,其中的elements字段是一个切片,用于存储队列的元素。Enqueue方法用于入队,通过调用append函数将元素追加到切片的末尾;Dequeue方法用于出队,它首先判断切片的长度是否为0,若为0则表示队列为空,返回false;否则,将切片的第一个元素取出并将切片的长度减1,然后返回该元素和true。

main函数中,我们创建了一个队列queue,并依次入队元素1、2和3。然后使用循环从队列中出队元素,并打印出来,直到队列为空。

2、使用container/list包实现队列

Go语言标准库中的container/list包提供了双向链表的实现,可以用来实现队列。下面是使用container/list包实现队列的示例代码:

package main

import (
    "container/list"
    "fmt"
)

func main() {
    queue := list.New()

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

    for queue.Len() > 0 {
        element := queue.Front()
        queue.Remove(element)
        fmt.Println(element.Value)
    }
}

在上述代码中,我们使用list.New()函数创建了一个新的双向链表,表示队列。PushBack方法用于入队,将元素添加到链表的末尾;Front方法用于获取链表的第一个元素

Remove方法用于移除链表中的指定元素。

main函数中,我们创建了一个队列queue,并依次入队元素1、2和3。然后使用循环从队列中出队元素,并打印出来,直到队列为空。

使用container/list包实现队列的好处是,它提供了更多的操作方法,例如PushFrontBackInsertBefore等,可以更灵活地操作队列。

以上就是使用切片和使用container/list包两种方式实现队列的示例代码。根据实际需求选择合适的实现方式,可以在Go语言中方便地使用队列来处理数据。

⬇️

在Go语言中,sync.Mutex是一种互斥锁,可以用于在多个协程之间对共享资源进行竞争访问的控制,从而实现并发控制。互斥锁的机制是,当一个协程获取到互斥锁之后,其他协程就不能再获取该锁,只能等待该协程释放锁之后才能获取。这样可以保证在同一时刻只有一个协程可以对共享资源进行访问,从而避免了并发访问时的资源冲突。

在上述的队列实现中,使用了sync.Mutex来实现对队列的并发控制,确保在多个协程同时对队列进行访问时,不会出现竞争访问的情况,从而保证了队列的数据安全性。

队列的优点:

  • 先进先出,符合实际场景

队列的缺点:

  • 随机访问速度慢
  • 实现比较复杂

队列的应用场景很广泛,特别适合需要按照顺序处理元素的情况。例如:

  1. 网络请求调度:在服务器端,可以使用队列来管理请求,保证按照接收顺序进行处理。
  2. 广度优先搜索:在图论和算法中,广度优先搜索常用队列来遍历节点,确保按层次遍历。
  3. 缓冲区管理:在生产者-消费者模型中,队列可以作为缓冲区,用于存储生产者产生的数据,供消费者消费。