You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell实现:判断坐标路径对是否构成连通图

解决Haskell中路径列表的连通性判断问题

我们可以把问题拆解为几个核心步骤,逐步实现isConnected函数:

1. 实现路径连通性判断的核心辅助函数

首先需要判断两条路径(线段)是否存在公共点,这包含三种情况:共享端点、其中一条的端点在另一条线段上、两条线段相交。

1.1 判断点是否在线段上

-- 检查点(x,y)是否在线段((x1,y1), (x2,y2))上
pointOnSegment :: (Integer, Integer) -> ((Integer, Integer), (Integer, Integer)) -> Bool
pointOnSegment (x,y) ((x1,y1), (x2,y2)) =
  -- 先判断点在 bounding box 范围内
  (min x1 x2 <= x && x <= max x1 x2) &&
  (min y1 y2 <= y && y <= max y1 y2) &&
  -- 叉积为0说明点与线段共线
  (x - x1) * (y2 - y1) == (y - y1) * (x2 - x1)

1.2 判断两条线段是否相交(非端点重合)

-- 计算三个点的叉积,用于判断方向
crossProduct :: (Integer, Integer) -> (Integer, Integer) -> (Integer, Integer) -> Integer
crossProduct (x1,y1) (x2,y2) (x3,y3) = (x2 - x1)*(y3 - y1) - (y2 - y1)*(x3 - x1)

direction :: (Integer, Integer) -> (Integer, Integer) -> (Integer, Integer) -> Integer
direction = crossProduct

-- 判断点q是否在线段pr上(用于处理相交的特殊情况)
onSegment :: (Integer, Integer) -> (Integer, Integer) -> (Integer, Integer) -> Bool
onSegment p q r =
  (min (fst p) (fst r) <= fst q && fst q <= max (fst p) (fst r)) &&
  (min (snd p) (snd r) <= snd q && snd q <= max (snd p) (snd r))

-- 判断两条线段是否相交(非端点重合场景)
segmentsIntersect :: ((Integer, Integer), (Integer, Integer)) -> ((Integer, Integer), (Integer, Integer)) -> Bool
segmentsIntersect (p1,p2) (p3,p4) =
  let d1 = direction p3 p4 p1
      d2 = direction p3 p4 p2
      d3 = direction p1 p2 p3
      d4 = direction p1 p2 p4
      -- 一般情况:两条线段互相跨立
      generalCase = (d1 * d2 < 0) && (d3 * d4 < 0)
      -- 特殊情况:端点落在另一条线段上(已被pathsConnected前置条件覆盖,保留兼容)
      specialCase = (d1 == 0 && onSegment p3 p1 p4) ||
                    (d2 == 0 && onSegment p3 p2 p4) ||
                    (d3 == 0 && onSegment p1 p3 p2) ||
                    (d4 == 0 && onSegment p1 p4 p2)
  in generalCase || specialCase

1.3 判断两条路径是否连通

pathsConnected :: ((Integer, Integer), (Integer, Integer)) -> ((Integer, Integer), (Integer, Integer)) -> Bool
pathsConnected p1@(a,b) p2@(c,d) =
  -- 任意端点落在对方线段上,或线段直接相交
  pointOnSegment a p2 || pointOnSegment b p2 ||
  pointOnSegment c p1 || pointOnSegment d p1 ||
  segmentsIntersect p1 p2

2. 实现全局连通性判断

我们可以用**广度优先搜索(BFS)**遍历所有连通的路径,最后检查是否覆盖了列表中所有路径:

import Data.List (delete)

-- 从起始路径出发,找到所有连通的路径
findConnected :: [((Integer, Integer), (Integer, Integer))] -> ((Integer, Integer), (Integer, Integer)) -> [((Integer, Integer), (Integer, Integer))]
findConnected paths start = bfs [start] []
  where
    bfs [] visited = visited
    bfs (current:rest) visited =
      let -- 筛选未访问且与当前路径连通的邻居
          neighbors = filter (\p -> p `notElem` visited && pathsConnected current p) paths
          newRest = rest ++ filter (`notElem` visited) neighbors
          newVisited = current : visited
      in bfs newRest newVisited

-- 主函数:判断所有路径是否属于同一个连通分量
isConnected :: [((Integer, Integer), (Integer, Integer))] -> Bool
isConnected [] = True  -- 空列表默认连通,可根据需求调整
isConnected (first:rest) =
  let allConnectedPaths = findConnected (first:rest) first
  in length allConnectedPaths == length (first:rest)

3. 测试示例

-- 连通示例,应返回True
example1 :: [((Integer, Integer), (Integer, Integer))]
example1 = [((0,0),(0,3)), ((0,2),(2,2)), ((2,1),(4,1)),
            ((4,0),(4,2)), ((4,2),(5,2)), ((2,1),(2,5)),
            ((1,5),(4,5))]

-- 非连通示例,应返回False
example2 :: [((Integer, Integer), (Integer, Integer))]
example2 = [((0,0),(0,3)), ((0,2),(2,2)), ((2,1),(4,1)),
            ((4,0),(4,2)), ((4,2),(5,2)), ((2,1),(2,5)),
            ((4,5),(5,5))]

可选优化:并查集(Union-Find)

如果处理大规模路径列表,BFS的elem操作效率较低,可以用并查集优化:

import Data.Map (Map)
import qualified Data.Map as Map
import Data.List (nub)

type UnionFind = Map Int Int

-- 查找节点根,带路径压缩
find :: UnionFind -> Int -> (UnionFind, Int)
find uf x =
  case Map.lookup x uf of
    Just parent | parent /= x ->
      let (uf', root) = find uf parent
      in (Map.insert x root uf', root)
    Just parent -> (uf, parent)
    Nothing -> (Map.insert x x uf, x)

-- 合并两个节点
union :: UnionFind -> Int -> Int -> UnionFind
union uf x y =
  let (uf', rootX) = find uf x
      (uf'', rootY) = find uf' y
  in if rootX == rootY then uf'' else Map.insert rootY rootX uf''

isConnectedUnionFind :: [((Integer, Integer), (Integer, Integer))] -> Bool
isConnectedUnionFind [] = True
isConnectedUnionFind paths =
  let initialUF = Map.fromList [(i,i) | i <- [0..length paths -1]]
      mergedUF = foldl (\uf (i,j) -> if pathsConnected (paths!!i) (paths!!j) then union uf i j else uf)
                      initialUF
                      [(i,j) | i <- [0..length paths-1], j <- [i+1..length paths-1]]
      roots = snd . unzip $ Map.toList $ snd $ foldl (\(uf,_) i -> find uf i) (mergedUF, 0) [0..length paths-1]
  in length (nub roots) == 1

内容的提问来源于stack exchange,提问作者Student

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 07:25:17