明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 2577|回复: 22

四叉树+哈希表应用之:连接直线,运行速度很快

  [复制链接]
发表于 2025-9-20 23:17:01 | 显示全部楼层 |阅读模式
本帖最后由 gzxl 于 2025-9-22 21:24 编辑



四叉树+哈希表应用之:连接直线,甚至可以是多段线、圆弧等等。
指定对角点: 找到 130300 个
连接成功后的条数为:2831
曲线连接用时 328 毫秒




以下是核心代码部分(代码结构有些像深度优先搜索 DFS)
  1. // 连接打散的线段(四叉树查找方式)
  2. // segments : 线段对象数组
  3. // polylines: 返回的点集数组
  4. // tol : 连接容差
  5. void CSegmentQtree::ConnectSegQ(vector<Segment>& segments, vector<GPolyline>& polylines, double tol)
  6. {
  7.     // 构建包围所有线段的矩形边界
  8.     double minX = 1e9, minY = 1e9, maxX = -1e9, maxY = -1e9;

  9.     for (size_t i = 0; i < segments.size(); ++i)
  10.     {
  11.         minX = min(minX, min(segments[i].ptStart.x, segments[i].ptEnd.x));
  12.         minY = min(minY, min(segments[i].ptStart.y, segments[i].ptEnd.y));
  13.         maxX = max(maxX, max(segments[i].ptStart.x, segments[i].ptEnd.x));
  14.         maxY = max(maxY, max(segments[i].ptStart.y, segments[i].ptEnd.y));
  15.     }

  16.     double margin = 10.0;  // 扩展边界
  17.     Rect world(minX - margin, minY - margin, maxX - minX + 2 * margin, maxY - minY + 2 * margin);

  18.     // 构建四叉树
  19.     CSegmentQtree quadtree(world);
  20.     // 构建哈希表
  21.     HashSegments mapSegments;

  22.     int nSuccess = 0;
  23.     for (size_t i = 0; i < segments.size(); ++i)
  24.     {
  25.         if (quadtree.Insert(&segments[i]))
  26.         {
  27.             // 哈希表加入该线段的索引值
  28.             mapSegments[segments[i]] = i;
  29.             nSuccess++;
  30.         }
  31.     }
  32.     //acutPrintf(_T("成功参与构建四叉树的线段共: %d 条\n"), nSuccess);

  33.     // 标记已处理的线段  true: 已访问
  34.     vector<bool> used(segments.size(), false);

  35.     for (size_t i = 0; i < segments.size(); ++i)
  36.     {
  37.         if (used[i])
  38.             continue;

  39.         GPolyline poly;
  40.         poly.push_back(segments[i].ptStart);
  41.         poly.push_back(segments[i].ptEnd);
  42.         used[i] = true;
  43.         
  44.         // 从线段的终点开始搜索
  45.         bool found = true;
  46.         while (found)
  47.         {
  48.             found = false;

  49.             // 获取当前折线最后一个点
  50.             Point lastPt = poly.back();
  51.             // 查询该点附近区域的线段(容差范围)
  52.             Rect queryRect(lastPt.x - tol, lastPt.y - tol, tol * 2, tol * 2);
  53.             vector<Segment*> nearbySegments;
  54.             quadtree.Query(queryRect, nearbySegments);
  55.             for (size_t j = 0; j < nearbySegments.size(); ++j)
  56.             {
  57.                 Segment* seg = nearbySegments[j];
  58.                 // 找到该线段在 segments 中的索引
  59.                 int idx = -1;
  60.                
  61.                 // 这线性遍历速度实在慢
  62.                 /*for (size_t k = 0; k < segments.size(); ++k)
  63.                 {
  64.                     if (&segments[k] == seg)
  65.                     {
  66.                         idx = k;
  67.                         break;
  68.                     }
  69.                 }*/
  70.                
  71.                 // 哈希表中查找该线段的索引值
  72.                 idx = mapSegments[*seg];

  73.                 if (idx != -1 && !used[idx])
  74.                 {
  75.                     if (AppendSegmentBack(poly, *seg, tol))
  76.                     {
  77.                         used[idx] = true;
  78.                         found = true;
  79.                         break;
  80.                     }
  81.                 }
  82.             }
  83.         }
  84.         
  85.         // 从线段的起点开始搜索
  86.         if (!found)
  87.         {
  88.             bool found = true;
  89.             while (found)
  90.             {
  91.                 found = false;

  92.                 // 获取当前折线开始一个点
  93.                 Point frontPt = poly.front();
  94.                 // 查询该点附近区域的线段(容差范围)
  95.                 Rect queryRect(frontPt.x - tol, frontPt.y - tol, tol * 2, tol * 2);
  96.                 vector<Segment*> nearbySegments;
  97.                 quadtree.Query(queryRect, nearbySegments);
  98.                 for (size_t j = 0; j < nearbySegments.size(); ++j)
  99.                 {
  100.                     Segment* seg = nearbySegments[j];
  101.                     
  102.                     // 找到该线段在 segments 中的索引
  103.                     int idx = -1;
  104.                     
  105.                     // 这线性遍历速度实在慢
  106.                     /*for (size_t k = 0; k < segments.size(); ++k)
  107.                     {
  108.                         if (&segments[k] == seg)
  109.                         {
  110.                             idx = k;
  111.                             break;
  112.                         }
  113.                     }
  114.                     */
  115.                     
  116.                     idx = mapSegments[*seg];

  117.                     if ((idx != -1) && !used[idx])
  118.                     {
  119.                         if (AppendSegmentFront(poly, *seg, tol))
  120.                         {
  121.                             used[idx] = true;
  122.                             found = true;
  123.                             break;
  124.                         }
  125.                     }
  126.                 }
  127.             }
  128.         }

  129.         if (!poly.empty())
  130.         {
  131.             polylines.push_back(poly);
  132.         }
  133.     }
  134. }
复制代码








本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x

评分

参与人数 5明经币 +5 金钱 +35 收起 理由
橡皮 + 1 赞一个!
highflybird + 1 + 30 赞一个!
yanshengjiang + 1
tigcat + 1 + 5 很给力!
zhoupeng220 + 1 很给力!

查看全部评分

回复

使用道具 举报

发表于 2025-9-22 20:31:08 | 显示全部楼层

我以前也写过类似的函数。也是用到了四叉树。
  1. //寻找首尾相连的曲线的子函数
  2.     static void findConnected(Point& pt, std::vector<AcDbObjectId>& outIds, std::set<AcDbObjectId> & visitedIds, std::set<Point> & points, QuadTree &qt, double tol = 1e-2)
  3.     {
  4.       visitedIds.insert(pt.id);
  5.       outIds.push_back(pt.id);
  6.       Rectangle recQuery;
  7.       recQuery.x = pt.x - tol;
  8.       recQuery.y = pt.y - tol;
  9.       recQuery.width = tol + tol;
  10.       recQuery.height = recQuery.width;
  11.       std::vector<Point> res;
  12.       qt.query(recQuery, res);
  13.       points.erase(pt);
  14.       Point ptOther(pt.ptOther.x, pt.ptOther.y, pt.id);
  15.       ptOther.ptOther = pt;
  16.       points.erase(ptOther);
  17.       if (!pt.bVisited)
  18.       {
  19.         recQuery.x = pt.ptOther.x - tol;
  20.         recQuery.y = pt.ptOther.y - tol;
  21.         qt.query(recQuery, res);
  22.       }
  23.       qt.remove(pt);
  24.       qt.remove(ptOther);

  25.       for (size_t i = 0; i < res.size(); ++i)
  26.       {
  27.         if (res.id == pt.id) continue;
  28.         std::set<AcDbObjectId>::iterator it = visitedIds.find(res.id);
  29.         if (it == visitedIds.end())
  30.         {
  31.           Point ptNew(res.ptOther.x, res.ptOther.y, res.id);
  32.           ptNew.ptOther = res;
  33.           ptNew.bVisited = true;
  34.           findConnected(ptNew, outIds, visitedIds, points, qt, tol);
  35.         }
  36.       }
  37.     }

  38.     //寻找首尾相连的曲线
  39.     static void findConnected(const std::vector<AcDbObjectId>& objs, std::vector<std::vector<AcDbObjectId>>& out, std::vector<AcDbObjectId> & closedIds)
  40.     {
  41.       Acad::ErrorStatus es;
  42.       AcDbExtents exts;
  43.       std::vector<Point> points;
  44.       for (size_t i = 0; i < objs.size(); ++i)
  45.       {
  46.         AcDbObjectId id = objs;
  47.         AcDbObjectPointer<AcDbCurve> curve(id, AcDb::kForRead);
  48.         es = curve.openStatus();
  49.         if (es != Acad::eOk) continue;
  50.         AcGePoint3d pt1, pt2;
  51.         curve->getStartPoint(pt1);
  52.         curve->getEndPoint(pt2);
  53.         pt1.z = 0; //除掉Z坐标
  54.         pt2.z = 0; //除掉Z坐标
  55.         if (curve->isClosed() || pt1.distanceTo(pt2) <= 1e-3)
  56.         {
  57.           closedIds.push_back(id);
  58.         }
  59.         else
  60.         {
  61.           AcDbExtents ext;
  62.           curve->getGeomExtents(ext);
  63.           exts.addExt(ext);
  64.           points.push_back(Point(pt1.x, pt1.y, id));
  65.           points.push_back(Point(pt2.x, pt2.y, id));
  66.           points[points.size() - 1].ptOther = pt1;
  67.           points[points.size() - 2].ptOther = pt2;
  68.         }
  69.       }

  70.       Rectangle rec;
  71.       AcGePoint3d ptMin = exts.minPoint();
  72.       AcGePoint3d ptMax = exts.maxPoint();
  73.       double width = ptMax.x - ptMin.x;
  74.       double height = ptMax.y - ptMin.y;
  75.       rec.x = ptMin.x - 10;
  76.       rec.y = ptMin.y - 10;
  77.       rec.width = width + 20;
  78.       rec.height = height + 20;

  79.       QuadTree qt(rec);
  80.       for (size_t i = 0; i < points.size(); i++)
  81.       {
  82.         qt.insert(points);
  83.       }

  84.       std::set<Point> setPoints;
  85.       for (size_t i = 0; i < points.size(); ++i)
  86.       {
  87.         setPoints.insert(points);
  88.       }

  89.       std::set<AcDbObjectId> visitedIds;
  90.       while (!setPoints.empty())
  91.       {
  92.         Point pt = *setPoints.begin();
  93.         std::vector<AcDbObjectId> ids;
  94.         findConnected(pt, ids, visitedIds, setPoints, qt);
  95.         out.push_back(ids);
  96.       }
  97.     }


评分

参与人数 1明经币 +1 收起 理由
gzxl + 1 测试图已发在一楼

查看全部评分

回复 支持 反对

使用道具 举报

发表于 2025-9-24 00:54:04 | 显示全部楼层
我在这里把核心代码贴出来
此算法采用了hash_map和DFS


  1. //方法二:采用hash_map
  2. void QT::QuadTree::findConnected(const std::vector<AcDbObjectId>& objs, std::vector<std::vector<AcDbObjectId> > & out)
  3. {
  4.   Acad::ErrorStatus es;
  5.   AcDbExtents exts;
  6.   std::vector<Point> points;
  7.   if (objs.empty()) return;

  8.   TimeCounter tt;
  9.   for (int i = 0, k = 0; i < (int)objs.size(); ++i)
  10.   {
  11.     AcDbObjectId id = objs;
  12.     AcDbObjectPointer<AcDbCurve> curve(id, AcDb::kForRead);
  13.     es = curve.openStatus();
  14.     if (es != Acad::eOk) continue;
  15.     Point pt1, pt2;
  16.     curve->getStartPoint(pt1);
  17.     curve->getEndPoint(pt2);
  18.     pt1.z = 0; //Z坐标归零
  19.     pt2.z = 0; //Z坐标归零
  20.     pt1.id = id;
  21.     pt2.id = id;
  22.     pt1.index = k++;
  23.     pt2.index = k++;
  24.     points.push_back(pt1);
  25.     points.push_back(pt2);
  26.     if (curve->isClosed() || pt1.distanceTo(pt2) < Point::pointTol)
  27.     {
  28.       points[k - 1].bClosed = true;
  29.       points[k - 2].bClosed = true;
  30.     }
  31.   }
  32.   tt.PrintTime(_T("收集"));

  33.   std::vector<Point*> pp(points.size());
  34.   for (size_t i =0; i<points.size();++i)
  35.   {
  36.     pp = & points;
  37.   }

  38.   for (size_t i =0; i<points.size();++i)
  39.   {
  40.     points.pNext = pp[i+1];
  41.     points[i+1].pNext = pp;
  42.     ++i;
  43.   }

  44.   //点按照先x后y从小到大排序,然后相同(相近)位置的分为一组
  45.   std::sort(pp.begin(),pp.end(),Point_Comp());
  46.   hash_map<int, std::vector<Point*> > hashPoints;
  47.   std::set<int> allGroups;
  48.   for (int i = 0, j = 0; i < (int)pp.size(); ++j)
  49.   {
  50.     Point* pt = pp;
  51.     pt->iGroup = j;
  52.     hashPoints[j].push_back(pt);
  53.     allGroups.insert(j);
  54.     i++;
  55.     for(;i<pp.size();++i)
  56.     {
  57.       if (pp->distanceTo(*pp[i-1]) > 1e-1)
  58.       {
  59.         break;
  60.       }
  61.       pp->iGroup = j;
  62.       hashPoints[j].push_back(pp);
  63.     }
  64.   }

  65.   //DFS 算法得出相连的线段
  66.   while (!allGroups.empty())
  67.   {
  68.     int i =  *allGroups.begin();
  69.     Point* pt = *(hashPoints.begin());
  70.     std::vector<AcDbObjectId> Ids;
  71.     findConnected(pt, hashPoints, Ids, allGroups);
  72.     out.push_back(Ids);
  73.   }
  74.   tt.PrintTime(_T("连接"));
  75. }

  76. //方法二的子程序
  77. void QT::QuadTree::findConnected(Point* pt, hash_map<int, std::vector<Point*> > & hashPoints, std::vector<AcDbObjectId>& outIds, std::set<int> & allGroups)
  78. {
  79.   if (pt->bVisited) return;
  80.   std::vector<Point*>& ptSame = hashPoints[pt->iGroup];
  81.   allGroups.erase(pt->iGroup);
  82.   for (size_t i = 0; i < ptSame.size(); ++i)
  83.   {
  84.     ptSame->bVisited = true;
  85.     if (!ptSame->pNext->bVisited)
  86.     {
  87.       outIds.push_back(ptSame->id);
  88.     }
  89.   }

  90.   for (size_t i = 0; i < ptSame.size(); ++i)
  91.   {
  92.     if (!ptSame->pNext->bVisited)
  93.     {
  94.       findConnected(ptSame->pNext, hashPoints, outIds, allGroups);
  95.     }
  96.   }
  97. }


评分

参与人数 1明经币 +1 收起 理由
gzxl + 1 神马都是浮云

查看全部评分

回复 支持 反对

使用道具 举报

发表于 2025-9-23 21:38:08 | 显示全部楼层
本帖最后由 highflybird 于 2025-9-23 23:08 编辑

优化了算法,不用四叉树,只是采用了哈希表。速度得到极大提升。




命令: LJ
选择对象: 指定对角点: 找到 130537 个
选择对象:
收集 用时: 0.032852 秒.  ---收集是指的采集线段的两个端点
连接 用时: 0.128950 秒.  ---连接是把相连的线段(包括曲线)连接起来。
找到了2509条相连的曲线。

和楼主同样的容差 ,最后合并的线条数更少。

容差设置为0.1的时候,合并的线条数我这边运行的结果是:2478

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x

评分

参与人数 1明经币 +1 收起 理由
gzxl + 1 赞一个!

查看全部评分

回复 支持 反对

使用道具 举报

发表于 2025-9-21 00:07:31 | 显示全部楼层
感谢大佬无私分享
回复 支持 反对

使用道具 举报

发表于 2025-9-21 12:21:12 | 显示全部楼层
感谢大佬无私奉献
回复 支持 反对

使用道具 举报

发表于 2025-9-22 01:26:10 | 显示全部楼层

感谢大佬无私奉献
回复 支持 反对

使用道具 举报

发表于 2025-9-22 10:50:23 | 显示全部楼层
大佬一出手就是神器
回复 支持 反对

使用道具 举报

发表于 2025-9-22 11:56:12 | 显示全部楼层
感谢大佬无私分享
回复 支持 反对

使用道具 举报

发表于 2025-9-22 20:59:26 | 显示全部楼层
楼主不妨发一个测试图,我验证一下我的代码的效率。
回复 支持 反对

使用道具 举报

发表于 2025-9-22 21:09:13 | 显示全部楼层
感谢大佬分享
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-9-22 21:26:09 | 显示全部楼层


1742669 条直线直接干废了。


输入曲线连接容差<0.001>:
请框选直线、圆弧、多段线:指定对角点: 找到 1742669 个
请框选直线、圆弧、多段线:
曲线连接用时 95172 毫秒
绘制连接线共: 13442 条,用时 250 毫秒

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2026-1-8 00:25 , Processed in 0.215666 second(s), 26 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表