繁体   English   中英

如何在 Go 中实现队列?

[英]How to implement a queue in Go?

当前的 Go 库不提供队列容器。 为了实现一个简单的队列,我使用圆数组作为底层数据结构。 它遵循 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())
    }   
}

在任何合理的版本中都不需要所有这些喧嚣(在1.x之后)。 切片可以实现一切。

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] = nil是好的; queue = queue [1:]让队列立即停止引用指针。

确实没有名为queue的包,但是vectorlist都可以排队。 另见这个问题

缓冲通道构成一个精细队列,尽管它具有固定的最大队列长度,在创建时选择。 一个通道具有有用的属性,该属性的出列是线程安全的(您的代码不是)。

Enqueue失败时,你仍然在增加p.tail ,所以下次它看起来不会失败 - 这解释了你的第一个循环中的单个false (并且为第二个循环弄乱了所有内容)。 最初的算法说OVERFLOW意味着“放弃一切”,而不是“只是继续前进就像没有发生任何不幸事件”;-)。

你需要做的就是减去p.tail如果你已经检查过发生了故障 - 或者将增加的值放在一个本地临时值中,只有在没有发生故障时才将其移到p.tail ,这可能更优雅。 这样一来,失败的Enqueue没有入队的新价值,但本身队列(即无溢值)仍是语义完整和正确的未来运营。

我修改原始实现以创建动态队列。 即,当队列填满时,它将分配更大的队列并移动所有项目。

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())
    }
}

首先,您需要为Queue创建一个结构体来保存队列属性。 然后创建一个initQueue函数来初始化默认值,这也将从用户那里获取内存大小。 创建一个函数来入值,创建函数来出队值。 创建一个显示函数来显示队列值。

type Queue struct {
    front  int
    rear   int
    size   int
    QArray []int
}

func (q *Queue) initQueue(size int) {
    q.size = size
    q.QArray = make([]int, q.size)
    q.front = -1
    q.rear = -1
}

func (q *Queue) enqueue(value int) {
    if q.rear == q.size-1 {
        fmt.Println("Queue is Full")
        return
    } else {
        q.rear++
        q.QArray[q.rear] = value
    }
}

func (q *Queue) dequeue() int {
    var x int = -1
    if q.front == q.rear {
        fmt.Println("Queue is Empty!")
    } else {
        q.front++
        x = q.QArray[q.front]
    }
    return x
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM