【问题标题】:Sorting nodes in a directed graph在有向图中对节点进行排序
【发布时间】:2021-06-29 09:29:48
【问题描述】:

对有向图的节点进行排序的最佳方法是什么?我已经用数组 B 的数组 A 制作了一个算法,其中数组 A 的元素代表列,数组 B 中元素的位置代表行。然后根据数组 A 和数组 B 中的位置将节点放置在网格中(例如,如果节点位于数组 B 中,该节点位于数组 A 的位置 2 和数组 B 的位置 3 中,那么它将被排序第 2 列和第 3 行并在那里渲染)。如果删除先前找到的根节点,则根据它们是否为根节点来确定列。

由于行的排序是随机的,您可以想象该图看起来并不太理想和有序。

图本身没有循环,基本上是一棵树,不同的是,一个子节点可以有多个父节点。

我和我的队友正在努力寻找一种对行进行最佳排序的方法,因此图表看起来最有序。 我们试图找到一个库,它可能只是以一种很好的方式对我们的节点进行排序,但到目前为止我们没有找到任何东西,这就是为什么我们现在尝试自己寻找解决方案。

第一个想法是,我们根据父节点的行来决定行。但是在边缘情况下它会变得相当复杂,在这种情况下,父节点和子节点之间存在大量连接,父节点有多个子节点,而子节点有多个父节点。

我们的代码只是获取一个节点和边的数组。然后根据它们的父节点和它们在树中的层次结构将它们分类到列中。

现在我们需要对它们进行垂直排序,尽可能让边缘不重叠。

nodeData.test.js

it('gets root node array and puts it in a grouped array, simpler test', () => {
    expect(
        getSortedNodes(
            // nodes:
            [{ id: 1 }, { id: 2 }, { id: 3 }],
            // edges:
            [
                { target: 3, source: 2 },
                { target: 3, source: 1 },
            ]
        )
    ).toStrictEqual([[{ id: 1 }, { id: 2 }], [{ id: 3 }]]) // resulting matrix
})

nodeData.js

//parameters when called: accumulated is an empty array, rootIds needs to be
//arrays of root Ids (which need to be extracted from nodes before calling function)
//edges is an array of edges, which contain source and target IDs
//thus this function is private and should only be called in getSortedNodes()
function sortNodes(accumulated, nodes, nodeIds, edges) {
    if (!nodeIds.length) {
        return accumulated
    }
    const targetList = edges.map(({ target }) => target)
    const rootIdLayer = difference(nodeIds, targetList, true)
    const rootNodes = nodes.filter(node => rootIdLayer.includes(node.id))
    const accumulatedList = [...accumulated]
    accumulatedList.push(rootNodes)
    const remainingNodeIds = difference(nodeIds, rootIdLayer, true)
    const remainingEdges = edges.filter(edge => !rootIdLayer.includes(edge.source))
    return sortNodes(accumulatedList, nodes, remainingNodeIds, remainingEdges)
}

// parameters take arrays or nodes and edges
// gives out sorted Array of nodes, which can be used to position those in a grid
export function getSortedNodes(nodes, edges) {
    const nodeIds = nodes.map(({ id }) => id)
    return sortNodes([], nodes, nodeIds, edges)
}

我们打算使用一个子节点更靠近其父节点的矩阵,而不是随机对齐的矩阵(请参阅单元测试)。

这里有一些伪代码来说明。 3 是 2 的孩子,4 是 3 的孩子。

nodes: 

[1,2,3,4]

edges:

[
    {source: 2, target: 3},
    {source: 3, target: 4}
]

expected result:

[
    [1, 2],
    [null, 3],
    [null, 4]
]

当前,丑陋的图表:

父母在孩子旁边的预期图表:

当边缘交叉时,这会变得更加复杂。


更新:我们决定尝试Sugiyama method。一旦我们有结果,我们将提供更新。此外,如果我们成功了,我们会提供答案。

如果您有任何我们可以尝试的建议或库,请随时发表评论或回答!

【问题讨论】:

  • 对不起,我很难理解你的问题。我建议您提供一些数据结构以及您想要实现的排序的不同示例。
  • 为了说明目的在底部添加了一些伪代码。现在好点了吗?
  • 更好。但我仍然不明白得到预期结果的逻辑是什么。预期结果是什么意思?
  • 有很多库可以计算带有大量花里胡哨配置的图形布局...您是否考虑过使用布局库(也可以选择使用它们进行渲染)?
  • 我现在明白了。但是,child nodes closer to their parents. 的定义并不明确。假设一个父节点可以链接到 3 个子节点,你如何定义哪个子节点应该更靠近父节点?

标签: javascript arrays sorting matrix graph


【解决方案1】:

我们最终使用dagre 来获取所有节点的坐标。这使我们可以删除所有定位和排序代码,只需使用dagre 提供的坐标即可。

import { graphlib, layout } from 'dagre'

const jsonToGraph = (nodes, edges) => {
    let graph = new graphlib.Graph({ directed: true })

    graph.setGraph({})
    graph.graph().rankDir = 'LR'
    graph.graph().ranksep = '60'

    graph.setDefaultEdgeLabel(function () {
        return {}
    })

    nodes.forEach(({ id, ...attributes }) => {
        graph.setNode(id, attributes)
    })

    edges.forEach(({ source, target, caption, visible }) => {
        graph.setEdge(source, target, { label: caption })
    })

    layout(graph)

    return graph
}
export default jsonToGraph

这是我们检索每个节点坐标的方式:

const { x, y, caption, type } = graph._nodes[id]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-16
    • 2016-02-07
    • 1970-01-01
    • 2019-05-21
    • 2018-04-12
    • 1970-01-01
    相关资源
    最近更新 更多