当前的Go库不提供队列容器。 为了实现一个简单的队列,我使用circle数组作为底层数据结构。 它遵循TAOCP中提到的算法:
Insert Y into queue X: X[R]<-Y; R<-(R+1)%M; if R=F then OVERFLOW.
Delete Y from queue X: if F=R then UNDERFLOW; Y<-X[F]; F<-(F+1) % M.
F: Front, R: Rear, M: Array length.
以下是代码:
package main
import (
"fmt"
)
type Queue struct {
len int
head, tail int
q []int
}
func New(n int) *Queue {
return &Queue{n, 0, 0, make([]int, n)}
}
func (p *Queue) Enqueue(x int) bool {
p.q[p.tail] = x
p.tail = (p.tail + 1) % p.len
return p.head != p.tail
}
func (p *Queue) Dequeue() (int, bool) {
if p.head == p.tail {
return 0, false
}
x := p.q[p.head]
p.head = (p.head + 1) % p.len
return x, true
}
func main() {
q := New(10)
for i := 1; i < 13; i++ {
fmt.Println(i, q.Enqueue(i))
}
fmt.Println()
for i := 1; i < 13; i++ {
fmt.Println(q.Dequeue())
}
}
但输出显然是错误的:
1是的 2真的 3真的 4真实 5真的 6真的 7真的 8真实 9真的 10假 11真的 12真的
11真的 12真的 0假 0假 0假 0假 0假 0假 0假 0假 0假 0假我想我还需要一个字段来使代码正常工作。 你有什么建议?
改进的代码有一个小缺点:大小为n的数组只能包含n-1个元素。
package main
import (
"fmt"
)
type Queue struct {
len int
head, tail int
q []int
}
func New(n int) *Queue {
return &Queue{n, 0, 0, make([]int, n)}
}
func (p *Queue) Enqueue(x int) bool {
p.q[p.tail] = x
ntail := (p.tail + 1) % p.len
ok := false
if ntail != p.head {
p.tail = ntail
ok = true
}
return ok
}
func (p *Queue) Dequeue() (int, bool) {
if p.head == p.tail {
return 0, false
}
x := p.q[p.head]
p.head = (p.head + 1) % p.len
return x, true
}
func main() {
q := New(10)
for i := 1; i < 13; i++ {
fmt.Println(i, q.Enqueue(i))
}
fmt.Println()
for i := 1; i < 13; i++ {
fmt.Println(q.Dequeue())
}
}
答案 0 :(得分:8)
在任何合理的版本中都不需要所有这些喧嚣(在1.x之后)。使用slices可以实现一切。
queue := []int{}
添加到队列:
queue = append(queue, 6)
从队列中弹出:
el := queue[0]
queue = queue[1:]
这是一个实现,表明pop不会花费很多时间(事实上这里它比push更短,因为我认为在队列增长时重新分配内存)。
package main
import (
"fmt"
"time"
)
func main() {
n := 10000000
queue := []int{1, 2, 3}
start := time.Now()
for i := 0; i < n; i++ {
queue = append(queue, i)
}
elapsed := time.Since(start)
fmt.Println(elapsed)
start = time.Now()
for i := 0; i < n; i++ {
_ = queue[0]
queue = queue[1:]
}
elapsed = time.Since(start)
fmt.Println(elapsed)
fmt.Println(queue)
}
在我的机器上,数字是:
216.611664ms
13.441106ms
来自 @DaveC 的评论:
这很简单,对于除关键代码之外的所有内容都非常有效 分配(对垃圾收集器施加压力)的地方 不好意思。要注意两件事,首先要确保重新分配 推送时的底层数组(虽然有效而不是每次调用) 直到发生这种情况,pop才会释放任何空间。这导致了 第二件事,如果(通常)队列包含指向 什么,然后排队[0] =零; queue = queue [1:] to 让队列立即停止引用指针。
答案 1 :(得分:2)
确实没有名为queue的包,但vector或list都可以排队。另请参阅this question。
答案 2 :(得分:1)
当Enqueue
失败时,您仍然递增p.tail
,因此下次看起来不会失败 - 这解释了单false
个你的第一个循环(并为第二个循环弄乱一切)。最初的算法说OVERFLOW
意思是“放弃一切”,而不是“只是继续前进,好像没有发生任何不幸事件”; - )。
如果您已检查发生了故障,则需要做的就是递减p.tail
- 或者将递增的值放在本地临时值中,只有在失败的情况下才将其移至p.tail
不发生,可能更优雅。这样,失败的Enqueue
使不将新值排入队列,但队列本身(没有溢出值)仍然在语义上完整无缺,并且对于将来的操作是正确的。
答案 3 :(得分:1)
缓冲通道构成一个精细队列,尽管它在创建时选择了固定的最大队列长度。一个通道有一个有用的属性,它出列是线程安全的(你的代码不是)。
答案 4 :(得分:0)
我修改原始实现以创建动态队列。即当队列填满时,它将分配一个更大的队列并移动所有项目。
package main
import (
"fmt"
)
type Queue struct {
len uint
head, tail uint
q []int
}
func NextPowerOfTwo(v uint) uint {
if v == 0 {
return 1
}
v--
v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
v++
return v
}
func NewQueue(n uint) *Queue {
n = NextPowerOfTwo(n)
if n < 4 {
n = 4
}
println("create queue of", n)
return &Queue{n, 0, 0, make([]int, n)}
}
func (p *Queue) Resize() {
if p.head == (p.tail + 1) % p.len {
new_len := p.len * 2;
new_q := make([]int, new_len)
// currently there are (len - 1) items in the queue
var i uint
for i = 0; i < p.len - 1; i++ {
n, _ := p.Dequeue()
new_q[i] = n
}
p.q = new_q
p.head, p.tail = 0, p.len - 1
p.len = new_len
println("queue resized to ", p.len)
}
}
func (p *Queue) Enqueue(x int) {
p.Resize();
p.q[p.tail] = x
p.tail = (p.tail + 1) % p.len
}
func (p *Queue) Dequeue() (int, bool) {
if p.head == p.tail {
return -1, false
}
x := p.q[p.head]
p.head = (p.head + 1) % p.len
return x, true
}
func main() {
q := NewQueue(1)
for i := 1; i < 13; i++ {
q.Enqueue(2 * i + 1)
println("enqueued item ", i)
}
println("** queue content **")
for i := 1; i < 13 + 1; i++ {
fmt.Println(q.Dequeue())
}
}