队列
三、队列 (顺序、循环、链式)
队列(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包实现队列的好处是,它提供了更多的操作方法,例如PushFront、Back、InsertBefore等,可以更灵活地操作队列。
以上就是使用切片和使用container/list包两种方式实现队列的示例代码。根据实际需求选择合适的实现方式,可以在Go语言中方便地使用队列来处理数据。
⬇️
在Go语言中,sync.Mutex是一种互斥锁,可以用于在多个协程之间对共享资源进行竞争访问的控制,从而实现并发控制。互斥锁的机制是,当一个协程获取到互斥锁之后,其他协程就不能再获取该锁,只能等待该协程释放锁之后才能获取。这样可以保证在同一时刻只有一个协程可以对共享资源进行访问,从而避免了并发访问时的资源冲突。
在上述的队列实现中,使用了sync.Mutex来实现对队列的并发控制,确保在多个协程同时对队列进行访问时,不会出现竞争访问的情况,从而保证了队列的数据安全性。
队列的优点:
- 先进先出,符合实际场景
队列的缺点:
- 随机访问速度慢
- 实现比较复杂
队列的应用场景很广泛,特别适合需要按照顺序处理元素的情况。例如:
- 网络请求调度:在服务器端,可以使用队列来管理请求,保证按照接收顺序进行处理。
- 广度优先搜索:在图论和算法中,广度优先搜索常用队列来遍历节点,确保按层次遍历。
- 缓冲区管理:在生产者-消费者模型中,队列可以作为缓冲区,用于存储生产者产生的数据,供消费者消费。