Go语言 求图中从给定顶点开始的所有闭行走的算法

qlzsbp2j  于 2023-04-27  发布在  Go
关注(0)|答案(1)|浏览(98)

我有一个无向的图,没有多个/平行的边,每个边都有一个距离。我希望在图中找到一个最小和最大总距离之间的闭合行走。我还希望能够设置行走中重复距离的限制,例如,如果一个10的边被遍历两次,重复距离为10。将重复距离设置为0应能在图形中找到闭合路径最后,虽然找到 * 所有 * 闭合的行走是很好的,但我不确定这对于有数千/数百万潜在路线的较大图是否可行,因为我相信这将需要指数级的时间。所以,我目前正试图找到几个根据启发式函数是最好的封闭行走,以便算法在合理的时间内完成。
编辑:近似性能要求-我用来测试的图目前有17,070个节点和23,026条边。生成的循环包含大约50条边,需要20秒的时间来查找,但是我希望在〈1分钟的时间范围内找到更长距离的循环(约100条边)。
以下是我目前的做法:
graph.go:

package graph

import (
    "fmt"

    "github.com/bgadrian/data-structures/priorityqueue"
)

// A Graph struct is a collection of nodes and edges represented as an adjacency list.
// Each edge has a weight (float64).

type Edge struct {
    HeuristicValue  int // Lower heuristic = higher priority
    Distance float64
}

type Graph struct {
    Edges map[int]map[int]Edge // adjacency list, accessed like Edges[fromId][toId]
}

func (g *Graph) FindAllLoops(start int, minDistance float64, maxDistance float64, maxRepeated float64, routesCap int) []Route {
    // Find all loops that start and end at node start.
    // A loop is a closed walk through the graph.
    
    loops := []Route{}
    loopCount := 0

    queue, _ := priorityqueue.NewHierarchicalHeap(100, 0, 100, false)
    
    queue.Enqueue(*NewRoute(start), 0)
    size := 1

    
    distanceToStart := getDistanceToStart(g, start)
    
    for size > 0 {
        // Pop the last element from the stack
        temp, _ := queue.Dequeue()
        size--
        route := temp.(Route)

        // Get the last node from the route
        lastNode := route.LastNode()
    
        // If the route is too long or has too much repeated distance,
        // don't bother exploring it further
        if route.Distance + distanceToStart[route.LastNode()] > maxDistance || route.RepeatedDistance > maxRepeated {
            continue
        }
        
        // Now we need to efficiently check if the total repeated distance is too long

        // If the last node is the start node and the route is long enough, add it to the list of loops
        if lastNode == start && route.Distance >= minDistance && route.Distance <= maxDistance {
            loops = append(loops, route)
            
            loopCount++
            if loopCount >= routesCap {
                return loops
            }
        }

        // Add all the neighbours of the last node to the stack
        for neighbour := range g.Edges[lastNode] {
            // newRoute will be a copy of the current route, but with the new node added
            newRoute := route.WithAddedNode(neighbour, g.Edges[lastNode][neighbour])
            
            queue.Enqueue(newRoute, newRoute.Heuristic())
            size++
        }
    }
    return loops
}

route.go:

package graph

import (
    "fmt"
)

// A route is a lightweight struct describing a route
// It is stored as a sequence of nodes (ints, which are the node ids)
// and a distance (float64)
// It also counts the total distance that is repeated (going over the same
// edge twice)
// To do this, it stores a list of edges that have been traversed before
type Route struct {
    Nodes    []int
    Distance float64
    Repeated []IntPair // two ints corresponding to the nodes, orderedd by (smallest, largest)
    RepeatedDistance float64
    Heuristic int // A heuristic for the type of Routes we would like to generate
    LastWay int
}

type IntPair struct {
    A int
    B int
}

// WithAddedNode adds a node to the route
func (r *Route) WithAddedNode(node int, edge Edge) Route {

    newRoute := Route{Nodes: make([]int, len(r.Nodes) + 1), Distance: r.Distance, Repeated: make([]IntPair, len(r.Repeated), len(r.Repeated) + 1), RepeatedDistance: r.RepeatedDistance, Ways: r.Ways, LastWay: r.LastWay}
    copy(newRoute.Nodes, r.Nodes)
    copy(newRoute.Repeated, r.Repeated)
    
    newRoute.Nodes[len(r.Nodes)] = node
    newRoute.Distance += edge.Distance
    
    // Get an IntPair where A is the smaller node id and B is the larger
    var pair IntPair
    if newRoute.Nodes[len(newRoute.Nodes)-2] < node {
        pair = IntPair{newRoute.Nodes[len(newRoute.Nodes)-2], node}
    } else {
        pair = IntPair{node, newRoute.Nodes[len(newRoute.Nodes)-2]}
    }
    
    // Check if the edge has been traversed before
    found := false
    for _, p := range newRoute.Repeated {
        if p == pair {
            newRoute.RepeatedDistance += edge.Distance
            found = true
            break
        }
    }

    if !found {
        newRoute.Repeated = append(newRoute.Repeated, pair)
    }
    
        // Current heuristic sums heuristics of edges but this may change in the future
        newRoute.Heuristic += edge.HeuristicValue
    
    return newRoute
}

func (r *Route) Heuristic() int {
    return r.Heuristic
}

func (r *Route) LastNode() int {
    return r.Nodes[len(r.Nodes)-1]
}

以下是我目前为加速算法所做的工作:
1.不是找到所有循环,而是根据给定的启发式算法找到前几个循环。
1.修剪图以尽可能删除几乎所有2度的节点,用权重=原始两条边之和的单条边替换它们。
1.使用切片存储重复边,而不是贴图。
1.使用Dijkstra算法找到从起始节点到每个其他节点的最短距离。如果一条路线无法在这个距离内返回,请立即丢弃它。(算法实现的代码没有包含在帖子中,因为它只占运行时间的4%)
根据Go分析器,route.go中的WithAddedNode方法占运行时的68%,该方法中的时间大约平均分配在runtime.memmove和runtime. makslice之间(调用runtime.mallocgc)。我相信大部分时间都花在复制路由上了。我非常感谢任何关于如何改进此算法的运行时间或任何可能的替代方法的反馈。如果有任何我可以提供的额外信息,请让我知道。非常感谢!

oug3syen

oug3syen1#

找到两个顶点之间所有路径的标准算法是:

  • 应用Dijkstra查找最便宜的路径
  • 路径中链路的增量成本
  • 重复操作,直到找不到新路径

在你的例子中,你想让路径返回到起始点,这个算法可以很容易地修改

  • 将起始顶点S拆分为两个,S1和S2。
  • 在连接到S的顶点上循环V。
  • 在S1和V之间添加零成本边
  • 在S2和V之间添加零成本边
  • 移除S及其边。
  • 运行标准算法查找S1和S2之间的所有路径

您需要修改Dijkstra代码以考虑其他约束-最大长度和“启发式”

性能

虽然你说你关心的是大图的性能,但你没有给予你所需的性能指标。
我对GO的性能一无所知,所以我假设它和我熟悉的C一样。
以下是C
的一些性能度量:
运行Dijkstra算法以在从https://dyngraphlab.github.io/下载的大型图上找到从一个随机选择的顶点到每个其他顶点的最便宜路径的运行时间。使用graphex应用程序运行3次的最长结果。
| 顶点计数|边缘计数|运行时间(秒)|
| --------------|--------------|--------------|
| 一万|五万|0.3|
| 十四万五千|二百二十万|四十|
假设你想找到10条封闭路径-将上面的运行时间乘以10。

相关问题