跳转至
开发版采用单机部署,无需产品 License 文件。需要多机部署或大规模性能测试? 了解企业版

最小生成树

本文介绍最小生成树算法,并演示如何用 GQL 实现该算法。

算法介绍

最小生成树用于在保持全图连通的前提下,使边权总和最小。

该过程将算法逻辑封装为可复用过程,可直接在图数据上执行并输出结果。

更多信息,请参见最小生成树。

数据模型

为了在 GQL 中实现最小生成树算法,使用以下数据模型创建图类型:

  • 实体由类型为Node的点表示,点属性包括id和rating。
  • 实体间关系由类型为Link的边表示,边属性包括weight。

该图类型的完整定义,请参见准备图数据。

过程实现

为了计算最小生成树结果,创建一个过程。

关于该过程的更多信息,请参见过程实现。下文将介绍该过程的关键组成部分及其工作原理。

该过程接受以下输入参数:

  • start_id:INT64类型。 最小生成树起始生长的点 ID。

此外,在过程内部定义了以下变量:

变量名 变量类型 描述
frontier 原始值变量 已加入 MST 的活跃点集合。
has_next 原始值变量 是否仍有跨越边可加入 MST。
step_no 原始值变量 当前 MST 边的步骤计数。
total_weight 原始值变量 已选 MST 边的累积总权重。
pick_src 原始值变量 所选最小跨越边的源点。
pick_dst 原始值变量 所选最小跨越边的目标点。
pick_weight 原始值变量 所选最小跨越边的权重。
best_edge 全局 SumAgg 聚合值变量 当前步骤中全局最小权重跨越边。
in_tree 点绑定 SumAgg 聚合值变量 每点标志:该点已是 MST 的一部分。
cand 点绑定 SumAgg 聚合值变量 到达该点的最轻跨越边候选。
result_table 表变量 存储 MST 边(step_no, src_id, dst_id, weight)的输出表。

以下步骤概述了该过程的逻辑:

  1. 从任意点启动 MST,将其加入frontier(树内集合)。
  2. 使用best_edge找出从树内点到树外点权重最小的跨越边。
  3. 将所选边及其目标点加入 MST,更新total_weight和step_no。
  4. 重复执行直至所有点都在树内或没有跨越边。返回 MST 边列表。

操作步骤

在悦数图管理的控制台中,按顺序执行以下代码块。首次使用时,先按准备算法示例数据确认操作位置和目录挂载。

1. 准备图数据

  1. 创建一个与数据模型匹配的图类型。

    '''
    CREATE GRAPH TYPE IF NOT EXISTS minimum_spanning_tree_graph_type AS {
        NODE Node (LABEL Node {id INT PRIMARY KEY, rating INT}),
        EDGE Link (Node)-[LABEL Link {weight INT, MULTIEDGE KEY()}]->(Node)
    }
    '''
    
  2. 下载以下 CSV 文件,保留文件名,保存到部署目录下的examples/algorithms/minimum_spanning_tree/:

    查看 CSV 内容
    minimum_spanning_tree_node.csv
    id,rating
    1,0
    2,2
    3,1
    4,0
    5,0
    6,2
    7,1
    8,2
    9,0
    10,1
    11,0
    12,2
    13,1
    14,1
    15,0
    16,2
    17,0
    18,1
    19,2
    20,0
    21,2
    22,1
    23,0
    24,1
    
    minimum_spanning_tree_edge.csv
    src_id,dst_id,weight
    1,2,4
    1,3,2
    1,4,7
    1,5,9
    2,3,1
    2,4,6
    2,5,5
    2,6,2
    3,4,3
    3,5,4
    3,6,8
    4,6,2
    4,7,6
    4,8,5
    5,6,4
    5,8,7
    5,9,6
    6,8,1
    6,9,5
    6,10,6
    7,9,2
    7,10,9
    8,9,3
    8,11,6
    8,12,4
    9,10,4
    9,12,7
    9,13,2
    10,12,1
    10,13,8
    10,14,4
    11,12,2
    11,14,5
    11,15,7
    12,14,3
    12,15,6
    12,16,5
    13,15,2
    13,16,7
    14,15,4
    14,17,8
    14,18,2
    15,16,1
    15,18,5
    15,19,3
    16,18,3
    16,19,6
    17,18,2
    17,20,7
    17,21,5
    18,19,4
    18,21,9
    18,22,6
    19,21,1
    19,22,5
    20,21,3
    20,23,6
    20,24,8
    21,22,2
    21,23,4
    21,24,7
    22,24,4
    23,24,1
    
  3. 基于图类型创建临时图,并将数据文件加载到临时图中。

    CREATE TEMPORARY GRAPH IF NOT EXISTS #minimum_spanning_tree_graph TYPED minimum_spanning_tree_graph_type
    
    '''
    USE #minimum_spanning_tree_graph IMPORT INTO GRAPH {
          NODE (v@Node {id: id, rating: rating}) FROM DATAFILE {
              PATH: "file:///examples/algorithms/minimum_spanning_tree/minimum_spanning_tree_node.csv",
              FORMAT: "CSV",
              DELIMITER: ','
          },
          EDGE (id: src_id)-[e@Link {weight: weight}]->(id: dst_id) FROM DATAFILE {
              PATH: "file:///examples/algorithms/minimum_spanning_tree/minimum_spanning_tree_edge.csv",
              FORMAT: "CSV",
              DELIMITER: ','
          }
      }
    '''
    

    导入成功后,在控制台执行以下查询核对点数:

    '''
    USE #minimum_spanning_tree_graph
    MATCH (n)
    RETURN count(n) AS node_count
    '''
    

    node_count应为24。点数不符时,先按文件问题排查检查文件和导入结果。

2. 创建过程

创建实现最小生成树算法的过程。关于该过程的更多信息,请参见过程实现。

'''
CREATE OR REPLACE PROCEDURE minimum_spanning_tree(start_id INT64)
RETURNS (step_no INT64, src_id INT64, dst_id INT64, weight DOUBLE, total_weight DOUBLE)
COMMENT "Compute a minimum spanning tree for the connected component that contains start_id in an undirected weighted graph whose vertex primary key is id and edge weight property is weight."
AS {
  VALUE frontier ACTIVE_SET

  VALUE has_next BOOLEAN = true
  VALUE step_no INT64 = 0
  VALUE total_weight DOUBLE = 0.0
  VALUE pick_src INT64 = 0
  VALUE pick_dst INT64 = 0
  VALUE pick_weight DOUBLE = 0.0

  VALUE best_edge TopKAgg<1, weight DOUBLE ASC, src_id INT64 ASC, dst_id INT64 ASC>

  NODE VALUE in_tree OrAgg = false
  NODE VALUE cand TopKAgg<1, weight DOUBLE ASC, src_id INT64 ASC, dst_id INT64 ASC>

  TABLE result_table TYPED TABLE {step_no INT64, src_id INT64, dst_id INT64, weight DOUBLE}

  MATCH (s)
  WHERE s.id = start_id
  PER NODE (s) {
    SET s.@in_tree = true
  }

  WHILE has_next = true THEN {
    MATCH (s)
    WHERE s.@in_tree = true
    PER NODE (s) {
      SET s.@in_tree = true
    }
    FINALLY {
      SET frontier = s
    }

    MATCH (t)
    WHERE t.@in_tree = false
    PER NODE (t) {
      SET t.@cand.clear()
    }

    SET @best_edge.clear()

    MATCH (s)-[e]-(t)
    WHERE s IN frontier AND t.@in_tree = false
    PER PATH {
      SET t.@cand += RECORD{weight: e.weight, src_id: s.id, dst_id: t.id}
    }

    MATCH (t)
    WHERE t.@in_tree = false AND t.@cand.size() > 0
    PER NODE (t) {
      SET @best_edge += t.@cand.min()
    }

    IF @best_edge.size() = 0 THEN {
      SET has_next = false
    }
    ELSE {
      SET pick_weight = @best_edge.min().weight
      SET pick_src = @best_edge.min().src_id
      SET pick_dst = @best_edge.min().dst_id
      SET step_no = step_no + 1
      SET total_weight = total_weight + pick_weight

      EXPORT step_no, pick_src, pick_dst, pick_weight INTO result_table

      MATCH (t)
      WHERE t.id = pick_dst
      PER NODE (t) {
        SET t.@in_tree = true
      }
    }
  }

  FOR r IN result_table
  RETURN
    r.step_no AS step_no,
    r.src_id AS src_id,
    r.dst_id AS dst_id,
    r.weight AS weight,
    total_weight AS total_weight
  ORDER BY step_no ASC
}
'''

3. 调用过程

从点1开始构建最小生成树,返回选中的边及总权重。

USE #minimum_spanning_tree_graph CALL minimum_spanning_tree(1) RETURN step_no, src_id, dst_id, weight, total_weight

返回:

展开查看返回
+---------+--------+--------+--------+--------------+
| step_no | src_id | dst_id | weight | total_weight |
+---------+--------+--------+--------+--------------+
| 12      | 19     | 21     | 1.0    | 49.0         |
| 23      | 23     | 24     | 1.0    | 49.0         |
| 22      | 21     | 23     | 4.0    | 49.0         |
| 21      | 3      | 5      | 4.0    | 49.0         |
| 20      | 21     | 20     | 3.0    | 49.0         |
| 19      | 12     | 11     | 2.0    | 49.0         |
| 18      | 12     | 10     | 1.0    | 49.0         |
| 17      | 14     | 12     | 3.0    | 49.0         |
| 16      | 18     | 17     | 2.0    | 49.0         |
| 15      | 18     | 14     | 2.0    | 49.0         |
| 14      | 16     | 18     | 3.0    | 49.0         |
| 13      | 21     | 22     | 2.0    | 49.0         |
| 1       | 1      | 3      | 2.0    | 49.0         |
| 11      | 15     | 19     | 3.0    | 49.0         |
| 10      | 15     | 16     | 1.0    | 49.0         |
| 9       | 13     | 15     | 2.0    | 49.0         |
| 8       | 9      | 13     | 2.0    | 49.0         |
| 7       | 9      | 7      | 2.0    | 49.0         |
| 6       | 8      | 9      | 3.0    | 49.0         |
| 5       | 6      | 4      | 2.0    | 49.0         |
| 4       | 6      | 8      | 1.0    | 49.0         |
| 3       | 2      | 6      | 2.0    | 49.0         |
| 2       | 3      | 2      | 1.0    | 49.0         |
+---------+--------+--------+--------+--------------+
保存结果到文件或图中

如需将返回的结果保存为 CSV 文件或写入图,请参见 EXPORT。导出字段应与返回列及目标属性对应。