Графы в компьютерной геометрии
Пример оптимизационной задачи: задача о минимальном остовном дереве
Связный граф без циклов называется деревом. Понятие дерева играет важную роль в приложениях, поскольку моделирует такой вид соединения своих вершин, в котором "нет ничего лишнего".
Подграфом H в графе называется такой граф , что и . Если граф G - взвешенный с весовой функцией , то для каждого его подграфа H естественно определяется его вес, равный сумме весов всех ребер этого подграфа.
Подграф называется остовным, если . В каждом связном графе, очевидно, имеется остовный подграф, являющийся деревом. Такие подграфы называются остовными деревьями. Количество разных остовных деревьев зависит от структуры графа. Для простого связного графа оно может быть вычислено с помощью так называемой матричной теоремы Кирхгофа. Интересующая нас точная оценка сверху имеет вид , где - количество вершин графа.
Остовное дерево наименьшего веса в связном взвешенном графе называется минимальным остовным деревом Несмотря на упомянутую оценку, имеются полиномиальные алгоритмы построения минимальных остовных деревьев. Одним из таких алгоритмов является алгоритм Краскала.
Пусть G = (V,E) - связный взвешенный граф с весовой функцией . Определим граф . На -м шаге, , среди всех ребер, добавление которых к порождает ацикличный граф, выберем ребро наименьшего веса и добавим к получив тем самым . Алгоритм заканчивает работу, построив граф , который и является минимальным остовным деревом.
В пакете Combinatorica имеется команда MinimumSpanningTree[<граф>], возвращающая минимальное остовное дерево в формате Graph.
Снова нарисуем красивые картинки:
In[74] := DynamicModule[{gu, gcur, mst, n = 11}, gu = RandomGraph[n, . 6] ; While [! ConnectedQ[gu], gu = RandomGraph[n, . 6] ] ; gcur = SetEdgeWeights[gu]; mst = MinimumSpanningTree[gcur]; Manipulate[plot3DWGraph[gcur, Edges[mst], r, {Glow[Red], Brown}], {r, 0.01, 0.2}], Initialization : -> (Needs [ "Combinatorica" " ] ; edgeWeight [ g_, e_] : = GetEdgeWeights [g, {e}]; plot3DWGraph[g_, 1_, r_, col_, op_:l] := Module[ {11} , 11 = 1≈ Join ≈ (RotateLeft[#, 1] & /@ 1) ; GraphPlot3D[g, EdgeRenderingFunction -> (If[MemberQ[ll, #2] , col, {Lighter[Hue[edgeWeight[g, #2]], .9], Opacity[op]}]≈ Join ≈ {Cylinder[#2, Max [ r edgeWeight [ g, #2], 0.01]]} &) , VertexRenderingFunction -> ({Yellow, Sphere[#, r+ 0.03]} &) , Boxed -> False] ] ) ]
In[75] := DynamicModule[{gu, gcur, mst, n = 11}, gu = RandomGraph[n, . 6] ; While[! ConnectedQfgu], gu = RandomGraph[n, . 6] ] ; gcur = SetEdgeWeights[gu]; mst = MinimumSpanningTree[gcur] ; Manipulate[plotWGraph[gcur, Edges[mst], r, Red, Orange], {r, 0.01, 0.1}], Initialization : -> (Needs ["Combinatorica" "] ; edgeW[g_, e_] : = GetEdgeWeights[g, {e}][[l]]; plotWGraph[g_, 1_, r_, coll_, col2_] :=Module[{11}, 11 = l ≈ Join ≈ (RotateLeft[#, 1] & /@1) ; GraphPlot[g, EdgeRenderingFunction -> (If [MemberQ[ll, #2], {col1, Thickness [r edgeW[g, #2]], Line[#2]}, {col2, Thickness [r edgeW[g, #2]], Opacity[0. 4] , Line[#2]}] &), VertexLabeling -> True] ] ) ]
Пример оптимизационной задачи: евклидовы минимальные остовные деревья, триангуляции Делоне, диаграммы Вороного
Если множество вершин графа содержится в метрическом пространстве , то весовая функция на ребрах такого графа естественно определяется как -расстояние между соответствующими вершинами. Минимальное остовное дерево (МОД) в полном графе с вершинами в метрическом пространстве называется метрическим МОД В частном случае, когда метрическое пространство - это евклидово пространство, а весовая функция -евклидово расстояние между точками, метрическое МОД называют ЕМО. В этом примере рассматривается случай евклидовой плоскости. Нам показалось удобнее самим написать процедуру рисования графов. Пунктир изображает полный граф. Отметим, что когда количество точек становится порядка 20 и выше, программа начинает работать все более медленно. Это связано с тем, что алгоритм Краскала имеет порядок сложности, пропорциональный квадрату количества ребер графа, т. е. для полного графа - четвертая степень от количества вершин. (Последнее связа но с тем, что количество ребер полного графа растет квадратично с ростом числа его вершин.) Тут на помощь приходит геометрия. Оказывается, ЕМОД (полного) графа всегда лежит в его специальном подграфе с линейным количеством ребер, а именно, в так называемой триангуляции Делоне:
In[76] : = DynamicModule[{pts, pCG, pts0, w, mst, ее, delauney, showTr}, pts0= {{1, 0}, {0, 1}, {-1, 0}}; Manipulate [vv = Map[{#, VertexNumber -> True} &, pts] ; pCG = SetEdgeWeights [Graph [CompleteGraph [Length[pts] ] [[!]] , vv] , WeightingFunction -> Euclidean] ; mst = MinimumSpanningTree[pCG]; ее = Edges[mst]; delauney = FromAdjacencyLists [#[[2]] & /@ DelaunayTriangulation [pts] ] ; Show[If[showTr, {showCG[Length[pts], pts, ее], showGr[delauney, pts, {Black}]}, showCG[Length[pts], pts, ее]] , PlotRange -> {{-2, 2}, {-2, 2}}], {{pts, pts0}, Locator, LocatorAutoCreate -> True} , {{showTr, False, "Показать триангуляцию Делоне"}, {True, False}}], Initialization: -> (Needs [ "Combinatorica'"] ; Needs["ComputationalGeometry'"]; showCG [ n_, v_, edg_: { } ] : = Graphics[GraphicsComplex[v, If [MemberQ [edg, #11]]], {Red, Thickness [0 . 015] , Opacity [0 . 7] , Line[#[[l]]] } , {Blue, Dotted, Line [#[[1]] ] } ] &/@ CompleteGraph [n] [[1]] ] ] ; showGr[g_, v_, col_: {Blue}] : = Graphics[GraphicsComplex[v, Append[col, Line[Edges[g] ] ]]];)]
Триангуляция Делоне тесно связана с так называемой диаграммой Вороного конечного подмножества M евклидова пространства. Если , то ячейкой Вороного точки называется множество для всех , где через , как и выше, обозначена функция расстояния. Ячейки Вороного точек двухточечного множества - суть полупространства (в двумерном случае - полуплоскости), порожденные серединным перпендикуляром к отрезку . В общем случае - это выпуклые многогранные области, полученные пересечением соответствующих полупространств:
In[77] := Manipulate[DiagramPlot[ pp] , {(pp/ {{0, 0}/ {1- 0}}}, Locator, LocatorAutoCreate -> True}]
Триангуляция Делоне - это граф на множестве точек M евклидова пространства, две вершины которого соединены ребром-отрезком, если и только если ячейки Вороного этих вершин пересекаются по множеству коразмерности один. В случае плоскости - по отрезку или лучу.
In[78] : = DynamicModule[{pts, pts0, vor, delauney}, pts0= {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; Manipulate[ delauney = FromAdjacencyLists [#|[2]] & /@ DelaunayTriangulation[pts] ] ; Show[{showDiaG[pts], showGr[delauney, pts, {Blue}]}, PlotRange -> {{-2, 2}, {-2,2}}], {{pts, pts0}, Locator, LocatorAutoCreate -> True}] , Initialization: -> (Needs [ "Combinatorica"'" ] ; Needs["ComputationalGeometry'" ] ; pairs [ls_] := Table [{Is [i] , Is |[i + 1]] } , {i , 1, Length [Is] - 1} ] ; showGr[g_, v_f col_: {Blue}] : = Graphics[GraphicsComplex[v, Append[col, Line [Edges [gr] ] ] ] ] ; showDiaGfpp ] := Module [ {hh, vd, In, edg, rs, els}, vd = VoronoiDiagram [pp] ; hh = Head[#] & /@ vd[[l]| ; In = Union[ Sort /@ Flatten [pairs [#] & /@ Select [ Table [ Select [vd [[2, i, 2] , hh[[#]| == List &] , {i, 1, Length [vd [[2]] }] , Length[#] > 1 &] , 1] ] ; edg = Line[vd[[1]][[#]] ] &/@ In; rs = Select [vd[[l]] , Head[#] == Ray &] ; Graphics [edg ≈ Join ≈ (Line /@ Apply [List, rs , {1}])]];)]
На следующей картинке ячейки Вороного раскрашены в разные цвета:
In[79] := DynamicModule [ {pts, pts0, vor, delauney}, pts0 = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; Manipulate[ delauney = FromAdjacencyLists[#[[2]] & /@ DelaunayTriangulation[pts]] ; Show[{showDia[pts], showGr[delauney, pts, {Blue}]}, PlotRange -> {{-2, 2} , {-2, 2}}] , {{pts, pts0}, Locator, LocatorAutoCreate -> True}] , Initialization: -> (Needs [ "Combinatorica" " ] ; Needs["ComputationalGeometry'"]; showGr[g_ , v_ , col_ : {Blue}] : = Graphics[GraphicsComplex[v, Append[col, Line [Edges [g]] ] ] ] ; append[l_, r_] := Module [ {bg, en, res}, If [Length[r] ≠ 4, res = 1 ≈ Join ≈ r, bg = r[[{l, 2}]; en = r[[{3, 4}]; Which[ bg[[1]] == 1[l] , res = {bgI2]; bgPI } ≈ Join ≈ 1, bg[[2]] == 1[[l] , res = bg ≈ Join ≈ 1, bg[[1]] == 1[[-l]], res = l ≈ Join ≈ bg, bg[[2]] == 1[[-1]], res = 1 ≈ Join ≈{bg[[2]] , bg[[1]]} ]; Which[ en[[1]] ==1[[1]], res = {enI2I , en III }≈ Join ≈res , en[[2]] == 1[[1]], res = en ≈ Join ≈ res, en[[1]] ==1[[-1]], res = res ≈ Join ≈ en, en[[2]] ==1[[-1]], res = res ≈ Join ≈ {en[[2]], en[[1]]]}] ]; res ]; showDia[pp_] : = Module[{hh, vd. In, rs, els, pi}, vd = VoronoiDiagram[pp] ; hh = Head[#] & /@vdlll ; ln = vdIlH#I & /@ Table [Select [vd[2, i, 2]| , hhl#] == List S] , {i, 1, Length[vdI2J]}]; rs = Map[If [Length[#] > 0, Flatten [Apply [List, it, 1] , 1] , {}] &, vd[[1]][[#]] & /@ Table [Select [vd [[2, i, 2]], hh[[#]] == Ray &] , {i, 1, Length[vd[[2]] ]}]] ; cls = MapThread[append[#1, #2] &, {In, rs}] ; pi = Polygon[#] & /@ cls; Graphics[Table[{EdgeForm[{Thick, Dashed}], ColorData[Length[pp], "ColorList"][[i]], Opacity[0.7], pl[[i]], Black, PointSize[0.02] , Point[pp[[i]] ] } , {i, 1, Length [pp]}]]]; ) ]
Возвращаясь к задаче о ЕМОД, с помощью триангуляции Делоне можно построить квадратичный (по количеству вершин) алгоритм построения ЕМОД. А именно, сначала строим триангуляцию Делоне, а затем уже к ней применяем алгоритм Краскала:
In [80] : = DynamicModule[{pts, ptsO, mst, ее, delauney, showTr}, pts0= {{1, 0}, {0, 1}, {-1, 0}}; showGr[g_, v_, col_: {Blue}] : = Graphics [GraphicsComplex[v, Append [ col, Line [Edges [g-] ] ]]]; Manipulate[ delauney = SetEdgeWeights[ FromAdjacencyLists [#[[2]] & /@ DelaunayTriangulation [pts] , pts] , WeightingFunction -> Euclidean] ; mst = MinimumSpanningTree[delauney] ; Show[ If[showTr, {showGr[mst, pts, {Red, Thickness[0.01], Opacity[0.7]}], showGr[delauney, pts, {Blue}]}, showGr[mst, pts, {Red, Thickness[0.01]}]], PlotRange -> {{-2, 2}, {-2, 2}}], {{pts, pts0}, Locator, LocatorAutoCreate -> True} , {{showTr, False, "Показать триангуляцию Делоне"}, {True, False}}], Initialization : -> (Needs [ "Combinatorica' " ] ; Needs["ComputationalGeometry *"]) ]
Замечание 7.3.1. На самом деле в общем случае алгоритм Краскала построения МОД работает со скоростью , а алгоритм построения ЕМОД, использующий триангуляцию Делоне, - со скоростью .
В качестве примера построим минимальные остовные деревья, соединяющие крупные города Германии и Великобритании. Напомним, Mathematica включает в себя большие базы данных по естественным наукам, в частности, по географии. Эти базы данных, впрочем, находятся на сайте компании Wolfram, поэтому, чтобы ими пользоваться непосредственно, нужен доступ к сети. В приведенных примерах мы просто выкачали информацию о геометрической форме страны и ее флаге из базы CountryData, и координаты и названия крупных (больше 100000 жителей) городов выбранных стран из базы CityData. Отметим также наличие баз данных по молекулярной биологии, астрономии, физике, химии, лингвистике, финансам и пр. (см. Scientific & Technical Data ).
In[81] : = germCitiesCoord = {{{52.52 ", 13.38 "}}, {{53.55 ", 10. '}}{* ... *) } ; ukCitiesCoord = {{{51.5", -0.1166667"}}, {{52.4666667", -1.9166667"}} (* . . . *)}; germCitiesNames = {"Berlin", "Hamburg", "Munich", "Cologne" (* ... *)}; ukCitiesNames = {"London", "Birmingham", "Glasgow", "Liverpool" (* ... *)}; germPoly = Polygon[{{{5.90725', 50.7242'}, {6.02985', 50.8203'}, {6.07395', 50.8833'}, {6.08445', 50.9323}, {6.05505', 50.9715}, {6.02985', 50.9785} (* ... *)}, {{13.9187', 53.886'}, {13.8735', 53.8918}, {13.87', 53.9166'} (. ... *)} (* ... *)}]; ukPolygon = Polygon[{{{-5.80035', 55.3191'}, {-5.83335', 55.3252'}, {-5.8559', 55.3505'}}, {{-7.1481', 55.1193-}, {-7.1613', 55.0969'}, {-7.154', 55.0852} (* ... *)} (* ... * }];
sph[v_] := {Cos [v[[ll] Degree] Cos [v|[2]] Degree] , Cos [v[[ll] Degree] Sin [v[[2I] Degree] , Sin[v[[ll] Degree] } ; geoDist[a_, b_] := N[2 ArcSin [Norm [sph [afl2]] ] - sph[b[[2]j] ] /2] ] ; Needs["Combinatoricav"]; geoMST [ cnt_] : = Module[{cts, ctsT, names, flag, poly, edges, gWeight, mst, col}, If[cnt == "Germany", cts = germCitiesCoord; names = germCitiesNames; flag = gflag; col = LightOrange; poly = germPoly, cts = ukCitiesCoord; names = ukCitiesNames; flag = ukflag; poly = ukPolygon; col = LightGray]; edges = Edges[CompleteGraph[Length[cts]] , All] ; gWeight = SetEdgeWeights[Graph[edges, cts] , WeightingFunction -> geoDist] ; mst = Edges[MinimumSpanningTree[gWeight]] ; ctsT = Reverse /@ Flatten [cts, 1] ; Graphics[{col, EdgeForm[Black], poly, Thick, Green, MapThread[Line[{#2, &2}] &, {ctsT[I^[[l]]I &/@mst, ctsT[I#[[2I]I &/@ mst}], PointSize [Medium] , Red, MapThread [Tooltip [Point [#1] , #2] &, {ctsT, names}] , Inset[flag, {Right, Top}, {Right, Top}, 2]}] ]; GraphicsRow [geoMST [#] & /@ { "Germany" , "UnitedKingdom" } ]