如何生成多個互不重疊的不同半徑圓?

如圖所示,採用什麼樣的思路處理才能防止隨機生成的圓之間產生重疊。

另補充一個條件,即,圓需按照一定半徑範圍生成,且能控制圓形面積與整體四邊形面積的比例,(如圓半徑範圍為1-2mm,圓形面積與整體面積之比為0.5)。此外,筆者還有一個疑問,如何對圓與圓之間是否產生接觸進行判斷?


這個問題就算沒人邀請,我也一定要答。有一次和公司一個美術扯皮,最後他竟然威脅我說:「我畫個圈圈詛咒你!」我立馬反擊道:「我寫個程序,畫一大坨圈圈詛咒你!而且我還會用N種方法畫。」

(1)第一種方法

寫個循環,循環中隨機生成一個圈圈的圓心坐標位置、半徑。然後判斷圈圈是否與已有的圈圈發生碰撞。如果有碰撞讓它滾蛋,否則畫圈圈。

(2)第二種方法

還是寫個循環,循環中隨機生成一個圈圈的圓心坐標位置,然後判斷這個坐標點是否在已有的圈圈內部,如果是就讓它滾蛋,否則計算出與它到距離最近圈圈的距離,並以此畫圈圈。

(3)第三種方法

有時需要將一堆指定半徑的圈圈,無碰撞的放置在某一空間中。這就需要一套物理碰撞演算法,感覺很難,不要怕,圈圈之間的碰撞檢測是所有碰撞檢測中最容易的。碰到這一情況,首先要找到一個中心點,然後將所有的圈圈按照與該中心點的距離遠近進行排序,依次將每一個圈圈朝向黨中心靠近,最後實現大和諧。

這種方法中需要克服的一個難點是,如何實現三個圈圈的兩兩相碰。解決這一問題,需要用到一個數學公式:餘弦定理,已知三角形的三邊長度,求三個角度。這算是我在實際編程中用過最高深的數學知識之一。

我寫的一個小遊戲:連泡泡用的就是這一方法。

滑鼠左鍵拖動泡泡到另一個與之相同顏色的泡泡旁邊,鬆開滑鼠左鍵,兩個泡泡就會自動合併。將所有相同顏色的泡泡連到一塊即過關。挺無聊的一個遊戲,玩多了容易得強迫症。(4)第四種方法

讓所有圈圈圍向一個中心,這樣太過集中,體現了獨裁專制,這政治不正確嗎。有時可能只是想讓圈圈放到一個大框里。這時的處理方式和前一個類似,只是修改一下圈圈的排序方式,讓圈圈按照朝向某一方向的遠近進行排序,並且朝著該方向移動。

我還寫過兩個更無聊的小遊戲:

掐泡泡,在這個遊戲中場景中含有若干個大小顏色不同的泡泡,同色泡泡碰撞後會合併成一個大點的泡泡,當泡泡大到一定程度後會破裂消失。泡泡會不停變多,滑鼠也需要不停地點擊泡泡。當泡泡擠到最下方時,遊戲結束。

打泡泡,和上個遊戲類似,場景中含有若干個大小顏色不同的泡泡,界面下方可以發射不同顏色的泡泡,擊中同色泡泡後會與之合併成一個大點的泡泡,當泡泡大到一定程度後會破裂消失。同樣,當泡泡擠到最下方時,遊戲結束。

(5)第五種方法

前四種方法,每添加一個圈圈時,都需要遍歷已有圈圈判斷是否與之碰撞。當圈圈數目增大時,效率不高啊。第五種方法可以輕鬆畫出大規模的圈圈:

先以固定步長生成規則的正方形網格點;

然後對網格點進行隨機移動,移動距離不要超過固定步長;

移動後的網格點為圈圈的圓心;

計算出每一個網格點與其周圍8個網格點的最近距離;

最近距離的一半為圈圈的半徑;

畫圈圈,就這麼簡單搞定。

發一下該演算法的C代碼:

inline void hash22(float px, float py, float hx, float hy)
{
float n = sinf(px * 7.0f + py * 157.0f);

hx = 2097152.0f*n;
hy = 262144.0f*n;

hx = hx - floorf(hx);
hy = hy - floorf(hy);
}

// 查找最近的相鄰頂點
float nearest_dis_2d(float x, float y)
{
float ox, oy;
hash22(x, y, ox, oy); // 當前頂點偏移

float d, r = 2.0f;
float vx, vy;
for(int i = -1; i &<= 1; i++) { for(int j = -1; j &<= 1; j++) { if (i == 0 j == 0) { continue; } hash22(i + x, j + y, vx, vy); vx += float(i) - ox; vy += float(j) - oy; d = vx*vx + vy*vy; if (d &< r) { r = d; } } } return sqrtf(r); } // 使用worley畫圈圈 float circle_worley2d(float x, float y) { float fx = floorf(x); float fy = floorf(y); float gx = x - fx; float gy = y - fy; float vx, vy; float d, r = 3.0f; float si, sj; for(int i = -1; i &<= 1; i++) { for(int j = -1; j &<= 1; j++) { hash22(i + fx, j + fy, vx, vy); vx += float(i) - gx; vy += float(j) - gy; d = vx*vx + vy*vy; if (d &< r) { r = d; si = i + fx; sj = j + fy; } } } d = sqrtf(r) / nearest_dis_2d(si, sj) * 2.0f; return d; }

代碼中調用函數circle_worley2d,輸入是平面上的一個二維坐標,返回值為1時,表示該坐標位於圓上。用上面的代碼可以生成如下圖像:

雖然這種方法對空間的利用率並不高,但它的運算複雜度與圈圈的數目無關。

(6)第六種方法:

上一種方法中,float circle_worley2d(float x, float y)函數,可以很容易地改成三維的

float circle_worley3d(float x, float y, float z)。

用三維的可以生成空間中若干個互相不重疊的球,如下圖所示:

並且利用circle_worley3d函數可以實現球面上畫圈圈:

當然,擴展到三維後的circle_worley3d函數,可以在任意一個三維圖形上畫圈圈。下圖為將許多圈圈畫在一個圈圈上:

(7)終極圈圈詛咒

球面上畫圈圈有什麼用處呢?可以生成隕石坑。

將生成圈圈的演算法迭代個若干次,就能生成一個布滿隕石坑的星球:

不過這個球不是我做的,生成它的代碼在:Shadertoy.

我生成的球是這樣:

當我看到這個星球時,有一種強烈的衝動:把這個球從顯示器中拿出來,然後砸在某人臉上。這滋味想想就覺得酸爽。


看到這個問題感覺特別感興趣,於是就照著 @葉飛影 和 @柳傾 提到的物理模擬的方法編了個MATLAB模擬程序,做了點微小的貢獻。模擬的結果大致是這樣的:

動圖鏈接:Imgur: The most awesome images on the InternetRemarks:

  1. 用了很簡單的spring-damper-mass模型。受 @葉飛影 的啟發,所有小球都受到向中心的引力。
  2. 這個簡單模型帶來的問題就是:所有的球或多或少都會有些形變,而且中心的球的形變尤為嚴重一些。假如要減小形變就得增大彈性係數,但那樣又會出現各種各樣的數值問題……球的半徑大小差距太大也會出現數值問題……總之,參數真心不好調。
  3. 模擬收斂速度大概是200個球不到一分鐘吧。
  4. 還沒有用到 @小侯飛氘 提到的元胞列表法,用的話應該可以加快模擬收斂速度吧。

以上。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~騙贊分割線~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

算了,估計也沒人點贊,還是直接給程序吧……

主程序:

鏈接:http://pan.baidu.com/s/1pL4g18v 密碼:mzhs

circle.m函數:

鏈接:http://pan.baidu.com/s/1gfBix2b 密碼:x9xw


本來想試試看各位的演算法的,就先自己擼了個暴力的做參考,結果秒出(不到0.1秒),就懶得試別的演算法了23333

r = 40;
[xData, yData, rData] = deal([]);
while r &> 3
x = rand * 600; y = rand * 600;
n = 0;
while any((x - xData) .^ 2 + (y - yData) .^ 2 &< (r + rData) .^ 2) x = rand * 600; y = rand * 600; n = n + 1; if n &> 200, r = r / 1.2; n = 0; end
end
xData(end + 1) = x; yData(end + 1) = y; rData(end + 1) = r;
end
t = (-15 : 15)" / 15 * pi;
patch(xData + cos(t) * rData, yData + sin(t) * rData, "w")
axis image off


考慮了一個稍微有點不同的問題:已知圓的個數及每個圓的半徑,找出一組圓心坐標使得這些圓互不重疊且盡量緊貼。

思路:當兩個圓有重疊時,給它們一個很大的排斥力;當兩個圓不重疊時,給它們一個相對小的吸引力;然後最小化總勢能。通過設計引力勢能等勢線的形狀,能大致控制最終結果的輪廓(像圓或者像方之類)。例子:

要最後像個圓形的話——

要最後像個方形的話——

Python 代碼:

import numpy as np
from scipy.optimize import minimize
from scipy.spatial.distance import pdist
import matplotlib.pyplot as plt
import os

def obj_grad_round(x, radii, pair_index_to_index_pair, repulsion_scale, attraction_scale):
n = len(radii)
centroids = np.reshape(x, (n, 2))
circles_pdist_sqrd = pdist(centroids, "sqeuclidean")
obj = 0
grad = np.zeros((n, 2))
for pidx, dist_sqrd in enumerate(circles_pdist_sqrd):
i, j = pair_index_to_index_pair[pidx]
touching_dist_sqrd = (radii[i] + radii[j]) ** 2
grad_inc = 4 * (dist_sqrd - touching_dist_sqrd) * (centroids[i, :] - centroids[j, :])
scale = repulsion_scale if dist_sqrd &< touching_dist_sqrd else attraction_scale obj += scale * (dist_sqrd - touching_dist_sqrd) ** 2 grad[i, :] += scale * grad_inc grad[j, :] -= scale * grad_inc return obj, grad.flatten() def obj_grad_square(x, radii, pair_index_to_index_pair, repulsion_scale, attraction_scale): n = len(radii) centroids = np.reshape(x, (n, 2)) circles_pdist_sqrd = pdist(centroids, "sqeuclidean") obj = 0 grad = np.zeros((n, 2)) for pidx, dist_sqrd in enumerate(circles_pdist_sqrd): i, j = pair_index_to_index_pair[pidx] touching_dist_sqrd = (radii[i] + radii[j]) ** 2 diff = centroids[i, :] - centroids[j, :] if dist_sqrd &< touching_dist_sqrd: # repulsion obj += repulsion_scale * (dist_sqrd - touching_dist_sqrd) ** 2 grad_inc = 4 * (dist_sqrd - touching_dist_sqrd) * diff grad[i, :] += repulsion_scale * grad_inc grad[j, :] -= repulsion_scale * grad_inc else: # attraction diff_sqrd = diff ** 2 if max(diff_sqrd) &>= touching_dist_sqrd:
# do not overlap after projection, needs attraction
if diff_sqrd[0] &>= diff_sqrd[1]:
obj_inc = (diff_sqrd[0] - touching_dist_sqrd) ** 2
grad_inc = np.array([4 * (diff_sqrd[0] - touching_dist_sqrd) * diff[0], 0])
else:
obj_inc = (diff_sqrd[1] - touching_dist_sqrd) ** 2
grad_inc = np.array([0, 4 * (diff_sqrd[1] - touching_dist_sqrd) * diff[1]])

obj += attraction_scale * obj_inc
grad[i, :] += attraction_scale * grad_inc
grad[j, :] -= attraction_scale * grad_inc

return obj, grad.flatten()

def find_layout(radii, shape="square", repulsion_scale=1e6, attraction_scale=1, attraction_decay=0.3, overlapping_tol=0., seed=None, verbose=False):
assert shape == "round" or shape == "square"
obj_grad = obj_grad_round if shape == "round" else obj_grad_square
n = len(radii)
n_pairs = int(n * (n-1) / 2)
pair_index_to_index_pair = [None] * n_pairs
pidx = 0
for i in range(n-1):
for j in range(i+1, n):
pair_index_to_index_pair[pidx] = i, j
pidx += 1

if seed:
np.random.seed(seed)

if verbose:
options = {"disp": True}
history_folder = "history_%s" % shape
if not os.path.exists(history_folder):
os.mkdir(history_folder)
else:
options = None

overlapped = True
n_trials = 1
x0 = np.random.randn(2 * n)

while overlapped:
if verbose:
print("repulsion scale = %g attraction scale = %g" % (repulsion_scale, attraction_scale))
opt_result = minimize(obj_grad, x0, args=(radii, pair_index_to_index_pair, repulsion_scale, attraction_scale), jac=True, options=options)
centroids = np.reshape(opt_result.x, (n, 2))
if verbose:
if not opt_result.success:
print(opt_result.message)
draw_layout(centroids, radii, "%s/%02d_attraction=%g.png" % (history_folder, n_trials, attraction_scale))
np.savetxt("%s/%02d_attraction=%g.dat" % (history_folder, n_trials, attraction_scale), centroids, fmt="%g")
overlapped = check_overlapping(centroids, radii, overlapping_tol, verbose)
if overlapped:
attraction_scale *= attraction_decay
n_trials += 1
x0 = opt_result.x
else:
overlapped = False

return centroids

def check_overlapping(centroids, radii, tol, verbose=False):
n = len(radii)
for i in range(n-1):
for j in range(i+1, n):
dij = np.linalg.norm(centroids[i, :] - centroids[j, :])
rhs = radii[i] + radii[j] - tol
if dij &< rhs: if verbose: print("Circle {i} and circle {j} overlap ({dij} = dist(C_{i}, C_{j}) &< r_{i} + r_{j} - tol = {rhs}).".format(i=i, j=j, dij=dij, rhs=rhs)) return True return False def draw_layout(centroids, radii, not_show_and_save_to=None): fig, ax = plt.subplots() for centroid, radius in zip(centroids, radii): circle = plt.Circle(centroid, radius) ax.add_artist(circle) x = centroids[:, 0] y = centroids[:, 1] xmin = min(x - radii) xmax = max(x + radii) ymin = min(y - radii) ymax = max(y + radii) plt.axis([xmin, xmax, ymin, ymax]) ax.set_aspect("equal") if not_show_and_save_to: plt.savefig(not_show_and_save_to) else: plt.show() plt.close() if __name__ == "__main__": seed = 1 np.random.seed(seed) n_circles = 100 radii = np.random.rand(n_circles) / 10 + 0.1 # [0.1, 0.2) overlapping_tol = 1e-4 shape = "square" # "round" or "square" verbose = False layout = find_layout(radii, shape, overlapping_tol=overlapping_tol, verbose=verbose) draw_layout(layout, radii, "final_%s.png" % shape) np.savetxt("radii.dat", radii, fmt="%g") np.savetxt("final_%s.dat" % shape, layout, fmt="%g")

.


grasshopper狗怒答

雖然有點取巧 不過感謝@孔德雨 的提示 新版的grasshopper確實有voronoi 的botton 不過grasshopper生成圓的原理暫時沒有根據voronoi vertices生成 但是注意到 有一個叫incircle的命令可以generate incircles in a triangle 需要的是三角形三個邊界點即可 這樣有兩種思路 一個是利用voronoi分解成幾個不重合的三角形 細分網格得到不重合的圓 這個具體我還沒有探索過....

第二個思路是利用delaunay網格直接生成三角形網格得到一系列不重合圓 經過一系列探索orz

得到如下結果

這個截圖是圓生成的base 即那個delaunay網格

不過我對第一種解法也很感興趣 有空探索一下

附上grasshopper電池圖

不知道為什麼會有runtime error 但是結果是沒錯的

以上

------------------------------一個小時候的分割線-----------------------------------------

感謝 @王喬志 的建議.....在每個vertex上塞了一個圓

原理是找到每個vertex 在它周圍的圓上project上的最短距離 以它為半徑生成圓保證不會交疊

這個說起來容易 做起來發現數據樹那是相當麻煩 以至於我一會兒graft一會flatten..... 以下是結果

這個是加上delaunay網格可以看到 每個三角形內生成的圓和每個vertex生成的圓互相不會交疊

然後友情附上grasshopper截圖....我連電池的習慣不太好 有時候會有點累贅 沒有coding基礎見諒

以上

------------------------更新的分割線--------------------------------

我來更新了!!!在過去的一年了學了kangaroo 發現有個新的解法 是利用kangaroo的 sphere collision來保證可以容納足夠多的圓而不重合 因為kangaroo是利用物理原理也就是說類似在一個容器里放進很多小球 所以他們一定不會重合....

截圖就不放了 感興趣的可以自己去了解一下kangaroo這個強大的插件


感謝 @王坤 在評論中的提醒,這應該是一個 Diffusion-limited aggregation 分形。粒子隨機聚集時就會表現出這種性質。

--------------------------------

更新了代碼,並添加了一些注釋

---------------------------------

在參數化設計課上用grasshopper做過一個類似的作業:如何用不同半徑的圓盡量鋪滿一個平面。分享一下我當時的解法:

  1. 先隨機生成兩個外切圓C1,C2;

  2. 生成一個隨機半徑r,分別在圓心O1, O2以半徑(R1+r), (R2+r)畫圓,得到第三個外切圓C3的圓心(隨機取其中一個交點);

  3. 對C2, C3(或C1, C3)重複以上過程。

核心思路就是這樣,具體實現的話還要考慮不能和已有的圓相交以及邊界問題。

生成的順序

grasshopper nodes

調試的過程中發現半徑的範圍和失敗次數的權重對整體圖案有較大的影響,不知道除了調參以外還有沒有其他的優化方法。一個意外的發現:如果除去邊界自由生長的話,會出現很多有趣的pattern:

import Rhino.Geometry as rg
import rhinoscriptsyntax as rs
import random

outCircles = [] #記錄所有生成的圓
tanCircle = [] #記錄所有圓的相切關係和生成失敗次數

# 檢測圓是否在界內
def withInBound(circle):
if not(hasBound): return True
circle = circle.ToNurbsCurve()
curveList = rg.Curve.CreateBooleanIntersection(circle,bound)
if (len(curveList) &> 0 ):
curve = curveList[0]
return curve.EpsilonEquals(circle,10**-10)
else:
return False

# 檢測是否和已有的圓相交
def notIntersect(circle1):
center1 = circle1.Center
r1 = circle1.Radius
for circle2 in outCircles:
center2 = circle2.Center
r2 = circle2.Radius
dist = rs.Distance(center1, center2)
if dist&<(r1+r2): return False return True #生成和C1,C2相切的圓 def generateCircle(index): circle1, circle2, fails = tanCircle[index] centerPoint = (circle1.Center + circle2.Center)/2 r = 1.5 * random.uniform(minRad, maxRad) x = random.choice([0,1]) for i in range(3): circle3 = rs.AddCircle(circle1.Center,circle1.Radius+r) circle4 = rs.AddCircle(circle2.Center,circle2.Radius+r) result = rs.CurveCurveIntersection(circle3,circle4) #求C3,C4交點(兩個) rs.DeleteObjects([circle3, circle4]) if (result == None): continue centerPoint = rg.Point3d(result[x][1]) circle5 = rg.Circle(centerPoint,r) #得到相切圓C5 if withInBound(circle5) and notIntersect(circle5): tanCircle.append((circle1, circle5, 0)) #記錄相切關係 tanCircle.append((circle2, circle5, 0)) outCircles.append(circle5) return True r *= 0.8 #如不成功則嘗試縮小半徑 #r = 1.5 * random.uniform(minRad, maxRad) x = not(x) #使用另外一個相切圓 tanCircle[index] = (circle1,circle2,fails+1) return False def debug(): print(i) x = [] y = [] z = [] for circle in outCircles: center = circle.Center x += [center[0]] y += [center[1]] z += [center[2]] fails= [] for circles in tanCircle: fails += [(outCircles.index(circles[0]),outCircles.index(circles[1]),circles[2])] return fails,x,y,z if (hasBound): bound = rs.coercecurve(bound) #第一個圓 center = rg.Point3d(0,0,0) r = 1.5 * random.uniform(minRad, maxRad) circle1 = rg.Circle(center,r) outCircles.append(circle1) #第二個圓 r = 1.5 * random.uniform(minRad, maxRad) center = rg.Point3d(circle1.Radius+r,0,0) circle2 = rg.Circle(center,r) outCircles.append(circle2) tanCircle.append((circle1,circle2,0)) #開始生成 for i in range(iteration): for j in range(len(tanCircle)): if (tanCircle[j][2] &< fails): generateCircle(j) fails,x,y,z = debug()


問題本質:

首先這是一個不等原packing問題,即利用不等大的圓去填充給定的空間(給定的空間可以是四邊形,三角形或者圓形)。搜索關鍵字 「不等圓packing」可以找到相關資料。 「不等原packing」 是一個NP 難度問題。

問題應用:

最簡單能想到的就是裁縫如何最大效率利用布料。

解決方法:

總結一下可能的解決方法,解決的問題和方式不完全和題目給定的一致,

(提問者是填充矩形,但下面以填充圓形為例,填充給定大小和個數的圓):

1. 最大穴位演算法(簡而言之將圓與其他圓相切的方式以此放下去)

2. 網格演算法(與網格細度有關)

3. 擬人擬物演算法(把小圓看做容器中有彈性的小球)

最後一種方法也是點贊比較多個答案所使用的方法,只是在填充比較高的時候還有很多需要考慮的地方,比如:

  • 如何跳出填充中次優的解(有些小圓無法塞進去)
  • 如何加速收斂因碰撞而產生的彈性勢能

這是五個高填充比時的算例,使用擬人擬物演算法。參考文獻 :

[1] 近世計算理論導引——NP 難度問題的背景、前景及其求解演算法研究, 科學出版社
[2] 基於格局變換策略的不等圓 Packing 問題求解演算法,計算機應用研究

[3] 解不等圓 packing 問題擬人擬物演算法初態選取,華中理工大學學報

[4] 支持求解原型 packing 問題的兩個擬人策略,中國科學

[5] 解 packing 及 CNF-SAT 問題的擬物擬人方法,華中理工大學學報

[6] 求解不等圓 packing 問題的一個啟發式演算法,計算機研究與發展


這問題前些年我其實考慮過,是在任意圖形中畫不相交的圓,思路大概如下:

  1. 得到一個任意形狀的區域
  2. 區域內任意生成一個點作圓心c
  3. 測量這個點到區域的最近距離d,並生成一個[0,d]值作為這個小圓的半徑r
  4. 從區域中摳掉這個小圓
  5. 重複2-4

當然,如果真的生成[0,d]的隨機半徑會發現遍地的極小圓,所以做了一丁點的修改,代碼如下

disk = Reap[
region =
RegionUnion[
BoundaryDiscretizeGraphics[
CountryData[#, "Polygon"]] /@ {"China", "Taiwan"}];
Do[p = RandomPoint[region];
rad = If[(tem = Abs[SignedRegionDistance[region, p]]) &< .2, tem, RandomReal[{.2, Min[{tem, Min@(Subtract @@ RegionBounds@region)/40}]}]]; region = RegionDifference[region, DiscretizeRegion@Sow[Disk[p, rad]]], 2500]][[-1, -1]]; Graphics[ Transpose[{RandomColor[ Hue[1/3, NormalDistribution[.6, .2], NormalDistribution[.6, .07]], disk // Length], disk}]]

關於勞德內三角畫內接,好像不能保證所有地方都隨機,空間利用率太低,不過我有些新的想法,如果有時間會更新到這個貼里


我的暴力破解思路:

循環隨機生成一些圓的中心點,看看如果沒碰撞到其他圓的話,就在本次循環中長大一點點

代碼:

import javax.swing.*;
import java.awt.*;
import java.util.*;

public class ProduceVariableCircle extends JFrame{
public class Circle{
int x;//泡泡X坐標
int y;//泡泡y坐標
int r = 0;//泡泡半徑
double iSpeed;//泡泡膨脹速度
boolean bGrow = true;//泡泡還有膨脹空間嗎
}
MyPanel mp=null;
ArrayList& CircleList = new ArrayList&();//現存泡泡集合

public static void main(String[] args) {
ProduceVariableCircle pvc=new ProduceVariableCircle();
}

public ProduceVariableCircle(){
//循環2000次
for(int iRepeatTime=0;iRepeatTime&<2000;iRepeatTime++){ //每次隨機生成100個泡泡中心點,加到現存泡泡集合里 for(int iNewAdd=0;iNewAdd&<100;iNewAdd++){ Circle tempCircle = new Circle(); tempCircle.x = (int)(Math.random()*600); tempCircle.y = (int)(Math.random()*600); tempCircle.iSpeed = 1+(int)(Math.random()*20); CircleList.add(tempCircle); } //逐一查看每個泡泡還有沒有膨脹空間 for(int i = CircleList.size()-1;i &>=0; i--){
//早就膨脹不了的,就不考慮了
if(!CircleList.get(i).bGrow){
continue;
}
//剩下的先膨脹一點點試試看
int r;
if(CircleList.get(i).r == 0){
r = (int)(CircleList.get(i).iSpeed);
}else{
r = (int)(CircleList.get(i).r*CircleList.get(i).iSpeed);
}
//一膨脹就馬上碰壁的,那就算了
int x = CircleList.get(i).x;
int y = CircleList.get(i).y;
if((x+r &> 600)||(x-r &< 0)||(y+r &> 600)||(y-r &< 0) ){ CircleList.get(i).bGrow = false; continue; } //否則的話可以檢測和其他的泡泡有沒有撞到 for(int j = 0;j &< CircleList.size(); j ++){ //那些個根本就不在這個泡泡周圍的,應該不用考慮了吧(性能考慮) int ox = CircleList.get(j).x; int oy = CircleList.get(j).y; int or = CircleList.get(j).r; if((or&<=0)||(Math.abs(x-ox) &> r+ or)||(Math.abs(y-oy) &> r+ or)){
continue;
}
//在周圍的泡泡,那就看看撞到了沒有,撞到的話那就算了
double iDistence = Math.sqrt(Math.pow((x - ox),2)
+Math.pow((y - oy),2));
if((iDistence &< or)||(iDistence &< r + or)){ CircleList.get(i).bGrow = false; break; } } //一路過關斬將,終於發現確實是有膨脹空間的,那就快快長大吧 if(CircleList.get(i).bGrow){ CircleList.get(i).r = r; } //還是一個點,就沒有膨脹空間了的話,那隻好拜拜了 if(!CircleList.get(i).bGrow CircleList.get(i).r==0){ CircleList.remove(i); } } } mp =new MyPanel(); this.add(mp); this.setSize(600, 600); this.setLocation(600, 300); this.setDefaultCloseOperation(this.EXIT_ON_CLOSE); this.setVisible(true); } class MyPanel extends JPanel{ public void paint(Graphics g){ super.paint(g); for(int i = 0;i &< CircleList.size(); i ++){ if(CircleList.get(i).r &>0 ){
g.drawOval((int)(CircleList.get(i).x-CircleList.get(i).r),
(int)(CircleList.get(i).y-CircleList.get(i).r),
(int)(2*CircleList.get(i).r),
(int)(2*CircleList.get(i).r));
}
}
}
}
}

上述代碼的效果如下:


我們以前考過類似的題……出題人應該是杜教。

可以這樣做:

  1. 我們先隨機生成一個遞減的序列,用來表示半徑。

  2. 接下來對於每個半徑,隨機出圓心的位置,如果能放下就直接放在那裡。這個隨機過程重複50次,如果每次都無法放下,則認為這個圓無法放下。

  3. 重複步驟1、2,直到無法操作為止。

優化:

「隨機生成一個遞減的序列」 改為 「每個半徑是上一個半徑的lambda倍」,然後不停地調參。

「重複50次」 改為 「第x次操作重複的次數,是第x-1次操作的重複次數的delta倍」,然後不停地調參。


gcj2012出過一道這樣的題,我當時做法是從左下角開始隨機圓心,然後做一個最大的外切圓,一直這樣直到平面滿了為止


且說說如何高效的判斷圓是否重疊。

很多物理模型中,高能粒子、原子、缺陷團簇乃至宏觀小球都採用圓或球來近似。當他們互相接近時會有相互作用,因此圓以及三維的球之間的碰撞判斷其實是非常有物理意義的一個問題。

前面很多答主給出了如何生成這些圓,但好像沒有答案討論如何高效的判斷圓與圓之間是否碰撞。而在分子動力學模擬中,有時需要對上百萬個原子進行上百萬步的模擬,每一步都要對每個原子進行這樣的判斷,判斷的效率自然是至關重要的。這裡我給出幾個演算法,拋磚引玉。

--------------------------------------------------

暴力枚舉法:每新添加一個圓,遍歷所有已存在的圓,判斷是否與新圓重疊。時間複雜度O(n)。

--------------------------------------------------

元胞鏈表法,(cell-list):把整個空間劃分為若干個網格,用鏈表建立起網格和圓之間的索引關係。添加新圓時,先確定新圓所在的網格,然後要遍歷包括該網格在內的附近9個網格內所有的圓即可(3維為27個)。元胞列表演算法的時間複雜度為O(1)。

--------------------------------------------------

多重元胞鏈表法:傳統的元胞鏈表法,網格劃分越細,需要遍歷的近鄰圓就越少,效率就越高。但是網格邊長的必須大於體系最大圓的直徑,否則就有可能漏判。例如下圖左圖,左邊的大黑圓為新加圓,紅色代表會被遍歷的圓,網格太細時,近鄰9個網格不足以覆蓋到右邊大圓的圓心,兩個大圓之間發生跨網格重疊,因此遍歷時便漏判了。因此,在類似小圓不斷聚合長大成大圓這樣的模擬中,需要設置很粗的網格,這樣的效率是較低的(如下圖中圖)。這種情況下,可以劃分一粗一細兩套並行的網格,並將細網格邊長設為臨界直徑。小於該直徑的圓僅僅被細網格索引,大於該直徑的圓僅僅被粗網格索引。在添加新圓時,需要遍歷兩套網格中的近鄰圓。儘管遍歷的格子數增加了,但實際需要遍歷的圓的數目是降低了的(如下圖右圖)。

--------------------------------------------------

Fortran coder,代碼就不貼了。

謝絕轉載。


問題加了限定條件,此方法可能不再適用。

====

生成點,構造三角形,找內切圓。


唔....你們都欽點了邊界是矩形嗎....

不必如此,雖然對於任意封閉曲線Omega 求其內接圓是極端困難的,但是如果是凸多邊形的話還是有高效演算法的,所以把邊界Omega 劃分成多邊形不就行了...

比較成熟的演算法有兩個:德勞內三角化沃羅諾伊網格演算法.

--------------------------------------------------------------------------------------------------------------------------------------------

positions=ImageValuePositions[EdgeDetect@img,White];
gra=Graphics[FilledCurve[Line[positions[[Last@FindShortestTour[positions]]]]]]
mesh=TriangulateMesh[BoundaryDiscretizeGraphics[gra,MaxCellMeasure-&>1],MaxCellMeasure-&>[Infinity]]
Graphics[MeshPrimitives[mesh,2]/.Polygon-&>Insphere]

唔....看來德勞內三角不大好用....估計是參數沒調好,細度太高了,劃分太多了...

沃羅諾伊網格的話因為要對多邊形求內接圓,慢得多,而且效果好不到哪裡去.

imgBounds=Transpose[{{0,0},ImageDimensions[img]}];
vm=VoronoiMesh[ImageValuePositions[EdgeDetect[img,5],White],imgBounds];
vml=NestList[VoronoiMesh[Mean@@@MeshPrimitives[#,2],imgBounds],vm,3];
Graphics[Table[{RGBColor[ImageValue[img,Mean@@p]],p},{p,MeshPrimitives[Last[vml],2]}]]

那隻好找一些慢一點的演算法了,比如K近鄰.最大穴演算法什麼的....

唉...對呀...想歪了,其實沒有什麼邊界條件只有約束條件,求有界區域Sigma ,在約束條件Omega 下的劃分...

Distance=Compile[{{pt,_Real,1},{Centers,_Real,2},{Radii,_Real,1}},(Sqrt[Abs[First@#-First@pt]^2+Abs[Last@#-Last@pt]^2]/@Centers)-Radii,Parallelization-&>True,CompilationTarget-&>"C",RuntimeOptions-&>"Speed"];
shape=ColorNegate@EdgeDetect[img,2]
Centers=Radii={};
Module[{dim,dt,pt,m,d,r},dim=ImageDimensions[shape];
dt=DistanceTransform[shape];
TimeConstrained[While[True,While[While[pt=RandomReal[{1,#}]/@dim;
(m=3+ImageValue[dt,pt])&<5]; (d=Min[Distance[pt,Centers,Radii]]-2)&<5]; r=Min[10,d,m];Centers=Join[Centers,{pt}]; Radii=Join[Radii,{r}]],10]]; Graphics@MapThread[Disk,{Centers,Radii}] Graphics[With[{col=RGBColor@ImageValue[img,RegionCentroid@#]},{EdgeForm@col,col,#}]/@MapThread[Disk,{Centers,Radii}]]

Ok...這麼奇異的約束條件都能解了那麼矩形這樣簡單的幾何圖形當然也能解咯...


隨機生成點,算voronoi集,畫內切圓。


用一方碗 乘一碗帶油的湯,湯上面的圓都是不一樣大且不會重疊的!


圓推擠是個非常有效而且簡單的東西


哈,這個簡單!

以下假定n為圓的數量。

首先如何在規定的[上限,下限]半徑範圍內生成那麼多的不同半徑:只需用(上限-下限)*(k-1)/(n-1)+上限 即可,其中k為第幾個圓。若n為1那就xjb畫就好了。

例如5個圓,半徑範圍為1-2mm,那麼則分別為1, 1.25, 1.5, 1.75, 2,保證不會有重複的半徑。

做完半徑後,接下來可以生成圓了。首先,我們在一望無際的x軸上平鋪這些圓。

其實這就滿足題面要求了,但是我們發現這對空間利用率不友好,接著就可以想想,蛋白質的機理(笑

我們可以把這一條鏈向內摺疊:

保持前兩個圓不動,將第三個圓繞著第二個圓滾,直到其與第一個圓相撞。

就像這樣。

滾動+碰撞判斷,這根本實現不了啊!換個思路,我們知道了三條邊的長度和兩個點坐標,第三個點的坐標其實很好求,只需要用到高中的餘弦定理知識就好。

第三個圓確定後,接下來如法炮製即可。效果:

時間複雜度為O(n)。

如果要離散,以第一個圓心和自己的圓心做射線向外移動隨機距離即可;如果要大小看起來不是很怪,將圓的半徑隨機減小一個小於半徑的值。注意減小後的半徑就有可能一樣了,用散列set記錄,還是能保證時間複雜度不變的。

這樣畫出來的圖形好像還是個分形圖形呢!

(手機上GeoGebra真難用


Mathematica的一種畫法,效率不高,主要是簡單

data=Table[{RandomReal[{0,10},2],RandomReal[{1/4,1}]},50];
disks=DeleteDuplicates[Disk@@@data,Not@*RegionDisjoint];
Graphics[{Circle@@@data,Opacity[0.2],disks}]


不邀自來~

用MATLAB寫了一個程序,隨機生成圓心和圓半徑,可以控制邊界範圍和圓的個數及半徑範圍,也是暴力檢查。但是好像沒有達到題主要求控制面積比例.....

%% Example
% This example illustrates how to draw circles randomly using MATLAB.

%% Preliminary work and settings
clc;
clear;

%Define the boundary of mian drawing domain
Xmax=1000;
Xmin=0;
Ymax=1000;
Ymin=0;

%The number of the circles needed to be drawn
Totalnum=200;

%The max and min radius values of a circle
Rmax=20;
Rmin=0;

%Initialization Parameters
Existnum=0; % The numbers of the existing circles
XPlist=[]; % The x position values of the center of existing circles
YPlist=[]; % The y position values of the center of existing circles
Rlist=[]; % The radius of the center of existing circles corresponding center positions

%% Drawing
figure;
box on;
axis equal;
hold on;
axis([Xmin Xmax Ymin Ymax]);
while 1
if Existnum&>Totalnum
break;
end
% Generate the radius and center position of a circle randomly
r=rand*(Rmax-Rmin)+Rmin;
xp=rand*(Xmax-Xmin)+Xmin;
yp=rand*(Ymax-Ymin)+Xmin;
% Check whether the standby circle is out of the boundarry
if xp+r&<=Xmax xp-r&>=Xmin yp+r&<=Ymax yp-r&>=Ymin
if Existnum==0
Existnum= Existnum+1;
rectangle("Position",[xp-r,yp-r,2*r,2*r],"Curvature",[1,1],"FaceColor",[rand,rand,rand]);
Rlist(Existnum)=r;
XPlist(Existnum)=xp;
YPlist(Existnum)=yp;
elseif Existnum~=0
% Check whether the standby circle is in existing circles
if all((XPlist-xp).^2+(YPlist-yp).^2&>=(Rlist+r).^2)
Existnum= Existnum+1;
rectangle("Position",[xp-r,yp-r,2*r,2*r],"Curvature",[1,1],"FaceColor",[rand,rand,rand]);
Rlist(Existnum)=r;
XPlist(Existnum)=xp;
YPlist(Existnum)=yp;
end
end
end
end

歡迎批評指正~


推薦閱讀:

stl partition演算法有兩種寫法,哪種效率高?
演算法導論第三版的翻譯水平如何?
該直接上《演算法導論》 還是先看完 《演算法 第四版》?
有能算出反三角函數的準確值的演算法嗎?

TAG:Python | 演算法 | MATLAB | WolframMathematica | Grasshopper |