问题描述
我有无向且可能断开连接的图,表示为边表。
我需要返回从给定的初始边集可到达的所有边的列表。
这是常见的任务,可以在许多网站上找到,带有 cycle
子句的递归查询在许多教程中都有。
特别在我脑海中的是:
与“手动”检测周期相比,cycle
子句在哪些方面更好?
示例:
1
1-----2
| /|
| / |
3| /5 |2
| / |
|/ |
3-----4
4
with graph (a,id,b) as (
select 1,1,2 from dual union all
select 2,2,4 from dual union all
select 1,3,3 from dual union all
select 3,4,4 from dual union all
select 2,5,3 from dual union all
select null,null,null from dual where 0=1
) --select * from graph;,input (id) as (
select column_value from table(sys.ku$_objnumset(2,4))
) --select * from input;,s (l,path,dup,seen,a,b) as ( -- solution using set of seen edges
select 0,'/' || g.id,cast(collect(to_number(g.id)) over () as sys.ku$_objnumset),g.a,g.id,g.b
from graph g
where g.id in (select i.id from input i)
union all
select s.l + 1,s.path || '/' || g.id,row_number() over (partition by g.id order by null),s.seen multiset union distinct cast(collect(to_number(g.id)) over () as sys.ku$_objnumset),g.b
from s
join graph g on s.id != g.id
and g.id not member of (select s.seen from dual)
and (s.a in (g.a,g.b) or s.b in (g.a,g.b))
where s.dup = 1
),c (l,b) as ( -- solution using cycle clause
select 0,g.b
from graph g
where g.id in (select i.id from input i)
union all
select c.l + 1,c.path || '/' || g.id,g.b
from c
join graph g on c.id != g.id
and (c.a in (g.a,g.b) or c.b in (g.a,g.b))
)
cycle id set is_cycle to 1 default 0
--select * from s; --6 rows
--select distinct id from s order by id; --5 rows
select * from c order by l; --214 rows (!)
--select distinct id from c where is_cycle = 0 order by id; --5 rows
CTE s
和 c
代表了 2 种不同的解决方案。
在这两种解决方案中,如果一条边有共同的顶点,则一条边是从另一条边扩展出来的。
解决方案 s
(基于集合)的工作原理类似于洪水。
由于 collect() over ()
子句,它基于特定递归级别上所有边的大量收集。
输入边在第 0 层,它们的邻居在第 1 层等。
每条边只属于一个级别。
由于父级别上的许多边(例如示例图中的边 5)的扩展,某些边可以在给定级别上多次出现,但使用 dup
列在下一级别消除了这些重复。
解决方案c
(基于cycle
子句)基于内置循环检测。
与解决方案 s
的实质性区别在于如何扩展下一个递归级别的行。
递归部分中的每一行都只知道来自前一个递归级别的单个祖先行的信息。
因此有很多重复,因为图遍历实际上会生成所有不同的游走。
例如,如果初始边是 {2,4},则它们中的每一个都不知道另一个边,因此边 2 扩展到边 4,边 4 扩展到边 2。类似地在此效果相乘的其他级别上。
cycle
子句仅消除给定行的祖先链中的重复项,而不考虑兄弟姐妹。
网络上的各种来源建议使用 distinct
或分析函数(参见 here)对如此庞大的结果集进行后处理。
根据我的经验,这并不能消除许多可能性的爆发。对于仍然很小的 65 条边的真实图,查询 c
没有完成,但查询 s
在数百毫秒内完成。
我很想知道为什么基于循环的解决方案在教程和文献中如此受欢迎。
我更喜欢使用标准方法,并且不会像我被教导的那样努力构建自己的周期检测解决方案 here,但是 s
解决方案对我来说效果更好,这让我有点困惑。 (请注意,s
解决方案的解释计划看起来更便宜。我也尝试过基于 Oracle 专有 connect by
的解决方案,但速度也很慢 - 为简洁起见,我在此处省略了它。)
我的问题是:
您是否看到 s
解决方案的任何实质性缺点,或者知道如何改进 c
解决方案以避免遍历不必要的组合?
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)