在Go中实现优先队列

huangapple go评论102阅读模式
英文:

Priority Queue Implementation in Go

问题

我刚刚看到了一种以一种“通用”的方式实现优先队列的方法,其中任何满足接口的类型都可以放入队列中。这是使用Go的正确方式吗?这会引入任何问题吗?

// Package prio provides a priority queue.
// The queue can hold elements that implement the two methods of prio.Interface.
package prio

/*
实现prio.Interface的类型可以插入到优先队列中。

最简单的用例如下:

    type myInt int

    func (x myInt) Less(y prio.Interface) bool { return x < y.(myInt) }
    func (x myInt) Index(i int)                {}

要使用Remove方法,您需要跟踪堆中元素的索引,例如:

    type myType struct {
            value int
            index int // 堆中的索引
    }

    func (x *myType) Less(y prio.Interface) bool { return x.value < y.(*myType).value }
    func (x *myType) Index(i int)                { x.index = i }

*/
type Interface interface {
// Less返回此元素是否应在元素x之前排序。
Less(x Interface) bool
// Index在此元素移动到索引i时由优先队列调用。
Index(i int)
}

// Queue表示一个优先队列。
// Queue的零值是一个空队列,可以立即使用。
type Queue struct {
h []Interface
}

// New返回一个初始化的优先队列,其中包含给定的元素。
// New(x...)的调用使用x的底层数组来实现队列,因此可能会更改x的元素。
// 复杂度为O(n),其中n = len(x)。
func New(x ...Interface) Queue {
q := Queue{x}
heapify(q.h)
return q
}

// Push将元素x推入队列。
// 复杂度为O(log(n)),其中n = q.Len()。
func (q *Queue) Push(x Interface) {
n := len(q.h)
q.h = append(q.h, x)
up(q.h, n) // up会执行x.Index(n)。
}

// Pop从队列中删除最小元素(根据Less)并返回它。
// 复杂度为O(log(n)),其中n = q.Len()。
func (q *Queue) Pop() Interface {
h := q.h
n := len(h) - 1
x := h[0]
h[0], h[n] = h[n], nil
h = h[:n]
if n > 0 {
down(h, 0) // down会执行h[0].Index(0)。
}
q.h = h
x.Index(-1) // 为了安全起见
return x
}

// Peek返回队列中的最小元素(根据Less),但不删除它。
func (q *Queue) Peek() Interface {
return q.h[0]
}

// Remove从队列中删除索引为i的元素并返回它。
// 复杂度为O(log(n)),其中n = q.Len()。
func (q *Queue) Remove(i int) Interface {
h := q.h
n := len(h) - 1
x := h[i]
h[i], h[n] = h[n], nil
h = h[:n]
if i < n {
down(h, i) // down会执行h[i].Index(i)。
up(h, i)
}
q.h = h
x.Index(-1) // 为了安全起见
return x
}

// Len返回队列中的元素数量。
func (q *Queue) Len() int {
return len(q.h)
}

// 在O(n)时间内建立堆不变量。
func heapify(h []Interface) {
n := len(h)
for i := n - 1; i >= n/2; i-- {
h[i].Index(i)
}
for i := n/2 - 1; i >= 0; i-- { // down会执行h[i].Index(i)。
down(h, i)
}
}

// 将位置i处的元素向堆顶移动以恢复不变量。
func up(h []Interface, i int) {
for {
parent := (i - 1) / 2
if i == 0 || h[parent].Less(h[i]) {
h[i].Index(i)
break
}
h[parent], h[i] = h[i], h[parent]
h[i].Index(i)
i = parent
}
}

// 将位置i处的元素向堆底移动以恢复不变量。
func down(h []Interface, i int) {
for {
n := len(h)
left := 2*i + 1
if left >= n {
h[i].Index(i)
break
}
j := left
if right := left + 1; right < n && h[right].Less(h[left]) {
j = right
}
if h[i].Less(h[j]) {
h[i].Index(i)
break
}
h[i], h[j] = h[j], h[i]
h[i].Index(i)
i = j
}
}

英文:

I have just seen an implementation of a priority queue in a generic kind of way in which any
type satisfying an interface can be put into the queue. Is this the way to go with go or does this introduces any issues?

// Copyright 2012 Stefan Nilsson
//
// Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package prio provides a priority queue.
// The queue can hold elements that implement the two methods of prio.Interface.
package prio

/*
A type that implements prio.Interface can be inserted into a priority queue.

The simplest use case looks like this:

        type myInt int

        func (x myInt) Less(y prio.Interface) bool { return x &lt; y.(myInt) }
        func (x myInt) Index(i int)                {}

To use the Remove method you need to keep track of the index of elements
in the heap, e.g. like this:

        type myType struct {
                value int
                index int // index in heap
        }

        func (x *myType) Less(y prio.Interface) bool { return x.value &lt; y.(*myType).value }
        func (x *myType) Index(i int)                { x.index = i }
*/
type Interface interface {
        // Less returns whether this element should sort before element x.
        Less(x Interface) bool
        // Index is called by the priority queue when this element is moved to index i.
        Index(i int)
}

// Queue represents a priority queue.
// The zero value for Queue is an empty queue ready to use.
type Queue struct {
        h []Interface
}

// New returns an initialized priority queue with the given elements.
// A call of the form New(x...) uses the underlying array of x to implement
// the queue and hence might change the elements of x.
// The complexity is O(n), where n = len(x).
func New(x ...Interface) Queue {
        q := Queue{x}
        heapify(q.h)
        return q
}

// Push pushes the element x onto the queue.
// The complexity is O(log(n)) where n = q.Len().
func (q *Queue) Push(x Interface) {
        n := len(q.h)
        q.h = append(q.h, x)
        up(q.h, n) // x.Index(n) is done by up.
}

// Pop removes a minimum element (according to Less) from the queue and returns it.
// The complexity is O(log(n)), where n = q.Len().
func (q *Queue) Pop() Interface {
        h := q.h
        n := len(h) - 1
        x := h[0]
        h[0], h[n] = h[n], nil
        h = h[:n]
        if n &gt; 0 {
                down(h, 0) // h[0].Index(0) is done by down.
        }
        q.h = h
        x.Index(-1) // for safety
        return x
}

// Peek returns, but does not remove, a minimum element (according to Less) of the queue.
func (q *Queue) Peek() Interface {
        return q.h[0]
}

// Remove removes the element at index i from the queue and returns it.
// The complexity is O(log(n)), where n = q.Len().
func (q *Queue) Remove(i int) Interface {
        h := q.h
        n := len(h) - 1
        x := h[i]
        h[i], h[n] = h[n], nil
        h = h[:n]
        if i &lt; n {
                down(h, i) // h[i].Index(i) is done by down.
                up(h, i)
        }
        q.h = h
        x.Index(-1) // for safety
        return x
}

// Len returns the number of elements in the queue.
func (q *Queue) Len() int {
        return len(q.h)
}

// Establishes the heap invariant in O(n) time.
func heapify(h []Interface) {
        n := len(h)
        for i := n - 1; i &gt;= n/2; i-- {
                h[i].Index(i)
        }
        for i := n/2 - 1; i &gt;= 0; i-- { // h[i].Index(i) is done by down.
                down(h, i)
        }
}

// Moves element at position i towards top of heap to restore invariant.
func up(h []Interface, i int) {
        for {
                parent := (i - 1) / 2
                if i == 0 || h[parent].Less(h[i]) {
                        h[i].Index(i)
                        break
                }
                h[parent], h[i] = h[i], h[parent]
                h[i].Index(i)
                i = parent
        }
}

// Moves element at position i towards bottom of heap to restore invariant.
func down(h []Interface, i int) {
        for {
                n := len(h)
                left := 2*i + 1
                if left &gt;= n {
                        h[i].Index(i)
                        break
                }
                j := left
                if right := left + 1; right &lt; n &amp;&amp; h[right].Less(h[left]) {
                        j = right
                }
                if h[i].Less(h[j]) {
                        h[i].Index(i)
                        break
                }
                h[i], h[j] = h[j], h[i]
                h[i].Index(i)
                i = j
        }
}

答案1

得分: 2

这个包通常不是一个好的选择,但如果你喜欢它并且它满足你的需求,可以使用它。我没有看到任何重大问题。

与container/heap相比,这个包的概念是将接口放在节点上而不是容器上。container/heap通过使用你的容器来提供更大的灵活性。(你可能已经在容器中有节点,而且该容器甚至可能不是一个切片。它只需要可索引。)另一方面,可能常见的情况是你不关心容器,愿意让包来管理它。这个包的索引管理是container/heap上的一个很好的特性,尽管它增加了一个方法调用的开销,即使不需要索引管理。

总是有权衡。container/heap非常通用。这个包通过较小的方法集(2个而不是5个)并在其上添加索引管理来实现,但可能会牺牲一些通用性和在某些情况下的一些性能。(如果你真的关心的话,你会想要进行基准测试。还有其他的差异可能会使索引调用的开销相形见绌。)

英文:

This package is not the way to go in general, but if you like it and it meets your needs, use it. I don't see any major issues.

The concept of this package compared to container/heap is to put the interface on the node rather than the container. Container/heap allows more flexibility by using your container. (You might have nodes in a container already, and that container might not even be a slice. It just has to be indexable.) On the other hand, it's probably a common case that you don't care about the container and would be happy to let the package manage it for you. The index management of this package is a nice feature over container/heap, although it adds the overhead of a method call even when index management is not needed.

There are always tradeoffs. Container/heap is very general. This package gets by with a smaller method set (2 instead of 5) and adds index management on top, but only by sacrificing a bit of generality and perhaps a bit of performance in some cases. (You'd want to benchmark if you really cared. There are other differences that may dwarf the overhead of the Index call.)

huangapple
  • 本文由 发表于 2012年12月8日 20:07:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/13777298.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定