【问题标题】:How to implement a queue in Go?如何在 Go 中实现队列?
【发布时间】:2011-03-03 19:24:46
【问题描述】:

当前的 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())
    }   
}

【问题讨论】:

标签: data-structures go queue


【解决方案1】:

Enqueue 失败时,你仍然增加p.tail,所以下次它似乎不会失败——这解释了你的第一个循环中的单个false(和混乱一切都准备好了第二个)。最初的算法说OVERFLOW 的意思是“放弃一切”,而不是“继续前进,就好像没有发生任何不幸事件一样”;-)。

您需要做的就是减少 p.tail(如果您已检查失败),或者将增加的值放在本地临时变量中,然后仅在失败时将其移动到 p.tail不是 发生,这可能更优雅。这样,失败的Enqueue 确实将新值排入队列,但队列本身(没有溢出的值)在语义上仍然是完整的,并且对于未来的操作是正确的。

【讨论】:

    【解决方案2】:

    确实没有名为 queue 的包,但 vectorlist 都可以组成很好的队列。另见this question

    【讨论】:

    • vector 可能不太好,因为从头插入或删除东西需要移动所有元素,这是非常低效的
    • @newacct 谢谢,你说得对。在这种情况下 list 肯定会更好,但 vector 无需修改仍然可以工作——只是效率不高。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        在任何合理的 go 版本(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 不会释放任何空间,直到发生这种情况。这导致 第二件事,如果(通常)队列包含一个指向 一些东西,那么最好做 queue[0] = nil;队列 = 队列 [1:] 到 让队列立即停止引用指针。

        【讨论】:

          【解决方案6】:

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

          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
          }
          

          【讨论】:

          • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
          猜你喜欢
          • 1970-01-01
          • 2012-11-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-18
          • 2017-04-17
          • 2017-07-22
          • 1970-01-01
          相关资源
          最近更新 更多