明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 34883|回复: 50

[越飞越高] 【飞鸟集】最小包围圆的最佳算法

    [复制链接]
发表于 2006-11-11 23:43:00 | 显示全部楼层 |阅读模式

这类话题本想在lisp开发版块中发布的,但因为与几何算法密切相关,故贴到此处。

最小包围圆的算法在实际和理论中都有值得探讨的必要。
在国内网站,对于此类算法鲜有介绍,今天完成了它的一个lisp程序,甚高兴。
《计算几何-算法与应用》中介绍的方法为随机增量式算法,可在O(n)的期望时间中算出来,
而这个算法有别于上种算法,其时间为O(|lg(d/R)|*n),也就是说很可能比上种算法时间更少。
请大家指教指教检查。附件为其lisp程序,加载,运行test然后选取点对象即可。

;;;************************************
;;;求最小包围圆的lisp程序--------------
;;;其算法为参见了有关文献--------------
;;;这种算法在退化很严重的情况结果也正确
;;;其中程序主段是核心算法,其他的附加程
;;;序为取点,画点,画圆和半径,用来测试
;;;************************************
(defun C:test (/ olderr en errmsg  oce
  oldmodessp ptlist x cen radius ptmax)
  ;;定义错误函数和预处理---------------
  (setvar "errno" 0)
  (setq olderr *error*)
  (defun *error* (msg / en errmsg)
    (setq en (getvar "errno"))
    (setq errmsg (strcat "errno=" (itoa en) "\nError:" msg))
    (alert errmsg)
    (setq *error* olderr)
  )
  (graphscr)
  (setq oldmode (getvar "osmode"))
  (setq oce (getvar "cmdecho"))
  (setvar "cmdecho" 0)
  (command ".ucs" "W")
  ;;取点,画点,并对函数用时计算-------
  (setq ssp (ssget '((0 . "POINT"))))
  (setq ptlist (ssgetpoint ssp))
  (setq t1 (getvar "CDATE"))
  (setq x (mincir ptlist))
  (setq t2 (getvar "CDATE"))
  (setq usetime (* (- t2 t1) 1e6))
  (princ (strcat "\n用时=" (rtos usetime 2 6) "秒"))
  (if (= nil x)
    (progn
      (alert "点的有效数目太小,请重新输入!")
      (command ".ucs" "p")
      (setvar "osmode" oldmode)
      (setvar "cmdecho" oce)
      (princ "\n")
      (princ)
    )
    (progn
      (setq cen (car x) radius (cadr x) ptmax (caddr x))
      ;;;画圆及半径,列出圆的圆心半径值
      (entmake
        (append
          '((0 . "circle") (100 . "AcDbEntity") (100 . "AcDbCircle"))
          (list (cons 10 cen))(list (cons 40 radius))(list (cons 62 1))
        )
      )
      (entmake
        (append
          '((0 . "line") (100 . "AcDbEntity") (100 . "AcDbLine"))
          (list (cons 10 cen))(list (cons 11 ptmax))(list (cons 62 1))
        )
      )
      (command ".ucs" "p")
      (setvar "osmode" oldmode)
      (setvar "cmdecho" oce)
      (princ "\n")
      (list cen radius)
    )
  )
)
;;;************************************
;;;求最小包围圆的函数,空集返回空集,否
;;;则返回最小圆的圆心,半径和圆上的一点
;;;这是程序的主段----------------------
;;;************************************
(defun mincir (ptlist / p1 p2 p3 ptmax cen_r cen radius)
  ;;定义中点函数,本来R2004版中无须定义
  ;;但不知道为什么到R2006版没有定义了。
  (defun mid (p1 p2)
    (if (or nil (= (length p1) 2) (= (length p2) 2))
      (list (/ (+ (car p1) (car p2)) 2.0) (/ (+ (cadr p1) (cadr p2)) 2.0) 0.0)
      (list (/ (+ (car p1) (car p2)) 2.0) (/ (+ (cadr p1) (cadr p2)) 2.0) (/ (+ (caddr p1) (caddr p2)) 2.0))
    )
  )
  ;;判断有效点个数---------------------
  (cond
    ((= (length ptlist) 0)
     nil
    )
    ((= (length ptlist) 1)
     (progn
       (alert "点集合为一点,最小圆半径为0")
       (list (car ptlist) 0 (car ptlist))
     )
    )
    ((= (length ptlist) 2)
     (progn
       (alert "点集合为两点,最小圆直径为其两点距离,\n圆心为其连线中点")
       (setq cen (mid (car ptlist) (cadr ptlist)) radius (/ (distance (car ptlist) (cadr ptlist)) 2))
       (list cen radius (car ptlist))
     )
    )
    (t
     (progn
       ;;上面啰嗦的一大段在实际情况中一般不会出现
       ;;判断点是否在圆内------------------------
       (defun in1 (pt cen r)
  (if (> (- r (distance pt cen)) (- 1e-8))
    t
    nil
  )
       )
       ;;判断点集是否在圆内----------------------
       (defun in2 (ptl cen r)
  (if (apply 'and (mapcar '(lambda (x) (in1 x cen r))  ptl))
    t
    nil
  )
       )
       ;;定义三点最小圆圆心及其半径,若是锐角三角
       ;;形,则是其三点圆,否则是其最大边的直径圆
       (defun 3pc (pa pb pc / a b c l p ja jb jc ppa ppb ppc cen radius)
  (setq a (list (distance pb pc) pa))
  (setq b (list (distance pc pa) pb))
  (setq c (list (distance pa pb) pc))
  (setq l (list a b c))
  (setq p (/ (+ (car a) (car b) (car c)) 2))
  (setq a (nth (car (vl-sort-i (mapcar 'car l) '>)) l))
  (setq b (nth (cadr (vl-sort-i (mapcar 'car l) '>)) l))
  (setq c (nth (caddr (vl-sort-i (mapcar 'car l) '>)) l))
  (setq l (+ (* (car b) (car b)) (* (car c) (car c)) (* (car a) (car a) -1.0)))
  ;;上面l利用了余弦定理作为判断-----------
  (if (< l 1e-8)
    (list (mid (cadr b) (cadr c))(/ (car a) 2)(list (cadr b) (cadr c) (cadr a)))
    (progn
      (setq ja (angle pb pc))
      (setq jb (angle pc pa))
      (setq jc (angle pa pb))
      (setq ppc (polar (mid pa pb) (+ (/ pi 2) jc) p))
      (setq ppa (polar (mid pb pc) (+ (/ pi 2) ja) p))
      (setq ppb (polar (mid pc pa) (+ (/ pi 2) jb) p))
      (setq cen (inters ppc (mid pa pb) ppa (mid pb pc) nil))
      (setq radius (distance cen pa))
      (list cen radius (list pa pb pc))
    )
  )
       )
       ;;定义四点的最小圆圆心半径,并返回三点坐标
       (defun 4pc (p1 p2 p3 ptmax / pts 3pt)
  (setq pts (list (3pc p1 p2 p3) (3pc p1 p2 ptmax) (3pc p1 p3 ptmax) (3pc p2 p3 ptmax)))
  (setq 3pt (vl-sort-i (mapcar 'cadr pts) '<))
  (setq pts (list (nth (car 3pt) pts)  (nth (cadr 3pt) pts)
           (nth (caddr 3pt) pts)(nth (cadddr 3pt) pts)))
  (nth (vl-position t (mapcar '(lambda (x) (in2 (list p1 p2 p3 ptmax) (car x) (cadr x))) pts)) pts)
       )
       ;;定义求点集中离圆心最远的点的函数--------
       (defun maxd-cir (ptl cen / distl)
  (setq distl (mapcar '(lambda (x) (distance x cen)) ptl))
  (nth (car (vl-sort-i distl '>)) ptl)
       )
       ;;开始递归运算----------------------------
       (setq p1 (car ptlist) p2 (cadr ptlist) p3 (caddr ptlist))
       (setq cen_r (3pc p1 p2 p3))
       (setq ptmax (maxd-cir ptlist (car cen_r)))
       (while (= nil (in1 ptmax (car cen_r) (cadr cen_r)))
         (setq cen_r (4pc p1 p2 p3 ptmax))
         (setq p1 (car (caddr cen_r)) p2 (cadr (caddr cen_r)) p3 (caddr (caddr cen_r)))
  (setq ptmax (maxd-cir ptlist (car cen_r)))
       )
       (list (car cen_r) (cadr cen_r) ptmax)
      );;for progn
    );;  for t   
  );;    for cond
);;      for defun
;;以下代码来自晓东
;;定义取点函数----
(defun ssgetpoint (ss / i listpp a b c)
  (setq i 0 listpp nil )
  (if ss
    (repeat (sslength ss)
      (setq a (ssname ss i))
      (setq b (entget a))
      (setq c (cdr (assoc 10 b)))
      (setq listpp (cons c listpp))
      (setq i (1+ i)) 
    )
  )
  listpp
)

本帖子中包含更多资源

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

x

评分

参与人数 1威望 +1 金钱 +10 贡献 +5 激情 +5 收起 理由
mccad + 1 + 10 + 5 + 5 【精华】好程序

查看全部评分

本帖被以下淘专辑推荐:

发表于 2010-7-13 11:20:00 | 显示全部楼层
改进了一下,速度提高不少
  1. ;;;************************************
  2. ;;;求最小包围圆的lisp程序--------------
  3. ;;;其算法为参见了有关文献--------------
  4. ;;;这种算法在退化很严重的情况结果也正确
  5. ;;;其中程序主段是核心算法,其他的附加程
  6. ;;;序为取点,画点,画圆和半径,用来测试
  7. ;;;************************************
  8. (defun C:test (/ CEN PTLIST PTMAX RADIUS SL SS T0 X)
  9.   ;;取点,画点,并对函数用时计算-------
  10.   (setq sl '((0 . "POINT,LINE,POLYLINE,LWPOLYLINE")))
  11.   (setq ss (ssget sl))
  12.   (setq ptlist (ssgetpoint ss))
  13.   (setq t0 (getvar "TDUSRTIMER"))
  14.   (setq x (mincir ptlist))
  15.   (princ "\n用时")
  16.   (princ (* (- (getvar "TDUSRTIMER") t0) 86400)) ;结束计时
  17.   (princ "秒")
  18.   (if (null x)
  19.     (alert "点的有效数目太小,请重新输入!")
  20.     (progn
  21.       (setq cen    (car x)
  22.      radius (cadr x)
  23.      ptmax  (caddr x)
  24.       )
  25.       ;;画圆及半径,列出圆的圆心半径值
  26.       (entmake
  27. (append
  28.    '((0 . "circle") (100 . "AcDbEntity") (100 . "AcDbCircle"))
  29.    (list (cons 10 cen))
  30.    (list (cons 40 radius))
  31.    (list (cons 62 1))
  32. )
  33.       )
  34.       (entmake
  35. (append
  36.    '((0 . "line") (100 . "AcDbEntity") (100 . "AcDbLine"))
  37.    (list (cons 10 cen))
  38.    (list (cons 11 ptmax))
  39.    (list (cons 62 1))
  40. )
  41.       )
  42.       (list cen radius)
  43.     )
  44.   )
  45. )
  46. ;;;************************************
  47. ;;;求最小包围圆的函数,空集返回空集,否
  48. ;;;则返回最小圆的圆心,半径和圆上的一点
  49. ;;;这是程序的主段----------------------
  50. ;;;************************************
  51. (defun mincir (ptlist / CEN CEN_R P1 P2 P3 PTMAX R RADIUS X i)
  52.   ;;判断有效点个数---------------------
  53.   (cond
  54.     ((= (length ptlist) 0)
  55.      nil
  56.     )
  57.     ((= (length ptlist) 1)
  58.      (alert "点集为一点,最小圆半径为0")
  59.      (list (car ptlist) 0 (car ptlist))
  60.     )
  61.     ((= (length ptlist) 2)
  62.      (alert "点集为两点,最小圆为过两点的圆")
  63.      (setq cen   (mid (car ptlist) (cadr ptlist))
  64.     radius (/ (distance (car ptlist) (cadr ptlist)) 2)
  65.      )
  66.      (list cen radius (car ptlist))
  67.     )
  68.     (t
  69.      ;;开始递归运算----------------------------
  70.      (setq p1 (car ptlist)
  71.     p2 (cadr ptlist)
  72.     p3 (caddr ptlist)
  73.      )
  74.      (setq cen_r (3pc p1 p2 p3))
  75.      (setq ptmax (maxd-cir ptlist (car cen_r)))
  76.      (setq i 0)
  77.      (while (null (in1 ptmax (car cen_r) (cadr cen_r)))
  78.        (setq cen_r (4pc p1 p2 p3 ptmax))
  79.        (setq p1 (car (caddr cen_r))
  80.       p2 (cadr (caddr cen_r))
  81.       p3 (caddr (caddr cen_r))
  82.        )
  83.        (setq ptmax (maxd-cir ptlist (car cen_r)))
  84.        (setq i (1+ i))
  85.      )
  86.      (list (car cen_r) (cadr cen_r) ptmax)
  87.     )
  88.   )
  89. )
  90. (defun make-line (p q)
  91.   (entmake
  92.     (list
  93.       '(0 . "LINE")
  94.       (cons 10 p)
  95.       (cons 11 q)
  96.     )
  97.   )
  98. )
  99. ;;以下代码来自晓东
  100. ;;定义取点函数----
  101. (defun ssgetpoint (ss / i l a b c)
  102.   (setq i 0)
  103.   (if ss
  104.     (repeat (sslength ss)
  105.       (setq a (ssname ss i))
  106.       (setq i (1+ i))
  107.       (setq b (entget a))
  108.       (setq c (cdr (assoc 10 b)))
  109.       (setq l (cons c l))
  110.     )
  111.   )
  112.   (reverse l)
  113. )
  114. (defun mid (p1 p2)
  115.   (list
  116.     (* (+ (car p1) (car p2)) 0.5)
  117.     (* (+ (cadr p1) (cadr p2)) 0.5)
  118.     (* (+ (caddr p1) (caddr p2)) 0.5)
  119.   )
  120. )
  121. ;;判断点是否在圆内------------------------
  122. (defun in1 (pt cen r)
  123.   (< (- (distance pt cen) r) 1e-8)
  124. )
  125. ;;判断点集是否在圆内----------------------
  126. (defun in2 (ptl cen r / pts pt)
  127.   (setq pts ptl)
  128.   (while (and (setq pt (car pts))
  129.        (in1 pt cen r)
  130.   )
  131.     (setq pts (cdr pts))
  132.   )
  133.   (null pt)
  134. )
  135. ;;定义三点最小圆圆心及其半径,若是锐角三角
  136. ;;形,则是其三点圆,否则是其最大边的直径圆
  137. (defun 3pc (pa pb pc / D MIDPT)
  138.   (cond  
  139.     ((in1 pc (setq midpt (mid pa pb)) (setq d (/ (distance pa pb) 2)))
  140.      (list midpt d (list pa pb pc))
  141.     )
  142.     ((in1 pa (setq midpt (mid pb pc)) (setq d (/ (distance pb pc) 2)))
  143.      (list midpt d (list pb pc pa))
  144.     )
  145.     ((in1 pb (setq midpt (mid pc pa)) (setq d (/ (distance pc pa) 2)))
  146.      (list midpt d (list pc pa pb))
  147.     )
  148.     (t
  149.       (3pcircle pa pb pc)
  150.     )
  151.   )
  152. )
  153. ;;; 三点圆函数
  154. (defun 3PCirCle (P0 P1 P2 / X0 Y0 X1 Y1 X2 Y2 DX1 DY1 DX2 DY2 D 2D C1 C2 CE)
  155.   (setq X0  (car  P0)
  156. Y0  (cadr P0)
  157. X1  (car  P1)
  158. Y1  (cadr P1)
  159. X2  (car  P2)
  160. Y2  (cadr P2)
  161. DX1 (- X1 X0)
  162. DY1 (- Y1 Y0)
  163. DX2 (- X2 X0)
  164. DY2 (- Y2 Y0)
  165.   )
  166.   (setq D (- (* DX1 DY2) (* DX2 DY1)))
  167.   (if (/= D 0.0)
  168.     (progn
  169.       (setq 2D (+ D D)
  170.      C1 (+ (* DX1 (+ X0 X1)) (* DY1 (+ Y0 Y1)))
  171.      C2 (+ (* DX2 (+ X0 X2)) (* DY2 (+ Y0 Y2)))
  172.      CE (List (/ (- (* C1 DY2) (* C2 DY1)) 2D)
  173.        (/ (- (* C2 DX1) (* C1 DX2)) 2D)
  174.         )
  175.       )
  176.       (list CE (distance CE P0) (list p0 p1 p2))
  177.     )
  178.   )
  179. )
  180. ;;定义四点的最小圆圆心半径,并返回三点坐标
  181. (defun 4pc (p1 p2 p3 ptmax / pts mind minr r 4ps)
  182.   (setq pts (list (3pc p1 p2 ptmax)
  183.     (3pc p1 p3 ptmax)
  184.     (3pc p2 p3 ptmax)
  185.      )
  186.   )
  187.   (setq 4ps (list p1 p2 p3 ptmax))
  188.   (setq minr 1e308)
  189.   (foreach n pts
  190.     (setq r (cadr n))
  191.     (if (and (< r minr)
  192.       (in2 4ps (car n) r)
  193. )
  194.       (setq mind n)
  195.     )
  196.   )
  197.   mind
  198. )
  199. ;;定义求点集中离圆心最远的点的函数--------
  200. (defun maxd-cir (ptl cen / pmax dmax d)
  201.   (setq dmax 0.0)
  202.   (foreach pt ptl
  203.     (if (> (setq d (distance pt cen)) dmax)
  204.       (setq dmax d
  205.      pmax pt
  206.       )
  207.     )
  208.   )
  209.   pmax
  210. )

评分

参与人数 1明经币 +1 收起 理由
669423907 + 1 很给力!

查看全部评分

回复 支持 1 反对 0

使用道具 举报

发表于 2010-7-19 19:21:00 | 显示全部楼层
曲高,难和啊
回复 支持 1 反对 0

使用道具 举报

发表于 2009-2-26 22:12:00 | 显示全部楼层
谢谢版主,我用MEASURE命令把多义线等分成N个点,再配合你的程序,终于可以画出任意多义线的最小外接圆了,误差很少,以后就不用天天画这个圆了,嘿嘿!不过用MEASURE描成N个点后有点慢。
回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2009-2-27 02:41:00 | 显示全部楼层

原以为这样的文章可能没多少实际用途,被冷藏了这么久。想不到还有点用处啊。

谢谢版主,我用MEASURE命令把多义线等分成N个点,再配合你的程序,终于可以画出任意多义线的最小外接圆了,误差很少,以后就不用天天画这个圆了,嘿嘿!不过用MEASURE描成N个点后有点慢。

下面的程序可以给你解决这个烦恼。

只需要输入test命令,然后你选择一个或者多个多段线,就给你画出来了。不用你去measure了。

另外因为没有仔细考虑优化的问题,所以精度最好不要输入过大(不输入也可以),测试1000和30000的结果都相差不大。

本帖子中包含更多资源

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

x
发表于 2010-7-16 17:01:00 | 显示全部楼层
:) 谢谢highlfybird版主的精彩代码

顺便学习了版主的C版代码


谢谢
发表于 2010-7-22 15:42:00 | 显示全部楼层
好东西,回去好好研究一下下。
发表于 2010-7-28 11:05:00 | 显示全部楼层
呵呵,感觉不错,
发表于 2010-9-2 11:22:00 | 显示全部楼层
思路不错,值得学习
发表于 2010-9-4 00:04:00 | 显示全部楼层
思路不错,值得学习
<script type="text/javascript">var reload=1;</script>
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-12-22 23:49 , Processed in 0.318610 second(s), 32 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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