我有以下问题:我有一个圆圈,其上有一定数量(零个或多个)的点。这些职位是固定的。现在我必须在圆上放置另一组点,例如所有点在圆周上尽可能均匀分布。
我的目标是开发一种算法,该算法采用角度列表(表示固定点)和int值(表示应放置多少个附加点)并再次返回角度列表(仅包含角度附加点应该是)。
这些点不必真正均匀分布(彼此距离相同),而是尽可能均匀分布。大多数时候可能不存在完美的解决方案,因为某些点是固定的。
所有角度的范围都在-pi和+ pi之间。
我想要实现的一些例子:
fixed_points = [-pi, -pi/2, pi/2]
v v v
|---------|---------|---------|---------|
-pi -pi/2 0 pi/2 pi
fill_circle(fixed_points, 1)
# should return: [0]
fill_circle(fixed_points, 2)
# should return: [-pi/6, pi/6]
或:
fixed_points = [-pi, -pi*3/4, -pi/4]
v v v
|---------|---------|---------|---------|
-pi -pi/2 0 pi/2 pi
fill_circle(fixed_points, 6)
这最后一个例子应该返回如下内容:一点是在-pi * 3/4和-pi / 4之间设置,即:-pi / 2并在-pi / 4之间分配其他5个点+ pi(记住它是一个圆圈,所以在这种情况下-pi = + pi):
v v x v x x x x x
|---------|---------|---------|---------|
-pi -pi/2 0 pi/2 pi
我开始使用递归算法,该算法首先搜索两点之间的最大间隔,并在两者之间设置新点。然而,它没有给出令人满意的结果。例如,考虑这种配置,需要插入两个点:
v v v
|---------|---------|---------|---------|
-pi -pi/2 0 pi/2 pi
first step: insert right in the middle of largest interval
v v x v
|---------|---------|---------|---------|
-pi -pi/2 0 pi/2 pi
second step: insert right in the middle of largest interval
-> all intervals are evenly large, so one of them will be taken
v x v v v
|---------|---------|---------|---------|
-pi -pi/2 0 pi/2 pi
不是一个非常好的解决方案,因为它可以更好地分布(见上文:-pi / 6和+ pi / 6)。
很抱歉这个问题很长,我希望你能理解我想要达成的目标。
我不需要一个完整的工作算法,而是开发一个正确的想法。如果你愿意,也许有些伪代码。非常感谢一些提示让我朝着正确的方向前进。提前谢谢!
谢谢大家的回答!它出现了我基本上只需要我已经存在的算法的非贪婪版本。我真的很喜欢haydenmuhls想法通过封装间隔/段类来简化问题:
class Segment:
def __init__(self, angle, prev_angle, wrap_around):
self.angle = angle
self.length = abs(angle - prev_angle + \
(2*math.pi if wrap_around else 0))
self.num_points = 0
def sub_length(self):
return self.length / (self.num_points + 1)
def next_sub_length(self):
return self.length / (self.num_points + 2)
def add_point(self):
self.num_points += 1
这使得实际算法非常简单易读:
def distribute(angles, n):
# No points given? Evenly distribute them around the circle
if len(angles) == 0:
return [2*math.pi / n * i - math.pi for i in xrange(n)]
# Sort the angles and split the circle into segments
s, pi, ret = sorted(angles), math.pi, []
segments = [Segment(s[i], s[i-1], i == 0) for i in xrange(len(s))]
# Calculate the length of all subsegments if the point
# would be added; take the largest to add the point to
for _ in xrange(n):
max(segments, key = lambda x: x.next_sub_length()).add_point()
# Split all segments and return angles of the points
for seg in segments:
for k in xrange(seg.num_points):
a = seg.angle - seg.sub_length() * (k + 1)
# Make sure all returned values are between -pi and +pi
ret.append(a - 2*pi if a > pi else a + 2*pi if a < -pi else a)
return ret
答案 0 :(得分:10)
假设您已经提供了M
个点,并且需要添加N
个点。如果所有点均匀分布,则它们之间会有2*pi/(N+M)
的间隙。因此,如果你在M
点切割以给出M
个角度段,你当然可以将点放入一个段(彼此间隔均匀),直到空间小于或等于{{ 1}}。
因此,如果细分受众群的长度为2*pi/(N+M)
,那么您应该将L
点放入其中。
现在你将剩下一些积分。如果添加了一个点,则按点之间的间距对线段进行排名。实际上将该点添加到排名最低的细分市场。将其中的一个重新插入到已排序的列表中,然后再次执行此操作,直到用完为止。
因为每次你将一个点放入一个段,其中结果将尽可能地间隔点,并且点之间的空间不依赖于你添加它们的顺序,你将最终得到最佳间距。
(编辑:其中“最佳”表示“点之间的最大最小距离”,即尽可能避免最坏情况下彼此重叠的点。)
(编辑:我希望很清楚,这个想法是决定每个段中有多少点,然后只有在最后,在数字全部被确定之后,你是否在每个段内平均分配它们。 )
答案 1 :(得分:5)
您可以使用Interval对象。间隔是两个原始不可移动点之间的圆弧。
以下只是伪代码。不要指望它可以在任何地方运行。
class Interval {
private length;
private point_count;
constructor(length) {
this.length = length;
this.point_count = 0;
}
public add_point() {
this.point_count++;
}
public length() {
return this.length;
}
// The current length of each sub-interval
public sub_length() {
return this.length / (this.point_count + 1);
}
// The sub-interval length if you were to add another point
public next_sub_length() {
return this.length / (this.point_count + 2);
}
public point_count() {
return this.point_count;
}
}
创建与圆上各点之间的间隔对应的这些对象的列表。每次添加一个点时,选择具有最大next_sub_length()的间隔。当你完成后,重建新圈子并不困难。
这应该为您提供最大可能的最小间隔。也就是说,如果您按照最小间隔的长度对解决方案进行评分,这将为您提供最高分数。我认为这就是你拍摄的内容。
编辑:刚刚意识到您在Python中专门询问了这一点。我是一个Python n00b,但你应该能够轻松地将它转换为Python对象,尽管你不需要getter,因为Python中的所有东西都是公共的。
答案 2 :(得分:4)
假设点之间的间隔是a_1 ... a_n。然后,如果我们将每个片段划分为最小尺寸d的片段,我们可以在片段中适合floor(a_i/d) - 1
个点。这意味着sum(floor(a/d) for a in interval_lengths)
必须大于或等于n + s
,其中n是我们要添加的点数,s是已存在的点数。我们希望选择尽可能大的d,最好只进行最佳d的二进制搜索。
一旦我们选择了d,只需逐步通过每个段每隔d度添加点,直到段中剩下不到2度的度数
修改您只需找到sum(floor(a/d) for a in interval_lengths) == n + s
,然后将floor(a_i/d) - 1
分配给每a_i/(floor(a_i/d) - 1)
度的段i。二进制搜索会很快找到。
进一步修改
以下是找到d
的代码def get_d(n, a, lo=0, hi=None):
s = len(a)
lo = 360./(s + n)
hi = 2. * lo
d = (lo + hi)/2
c = sum(floor(x/d) for x in a)
while c != (n + s):
if c < (n + s):
hi = mid
else:
lo = mid
d = (lo + hi)/2
c = sum(floor(x/d) for x in a)
return d
答案 3 :(得分:2)
首先,我们将术语重新定义如下:
找到N个点的这种分布,这两个点中的任何一个与M预定义之间的最小距离的长度是最大的。
所以你的任务是找到这个最小长度的最大值。称之为L
您有M段现有段,假设它们存储在列表s
中。所以如果这个长度首先是L
min(s) > L
和最多额外点数是
f(L) = sum(ls/L -1 for ls in s)
因此,您可以使用二进制搜索找到最优L,其中起始最小值L = 0且最大值L = min(s),并且如果sum(ls / L -1表示s中的ls)> = N,则检查条件。 然后对于每个段s [i],你可以均匀地放置s [i] / L -1个点。 我认为这是最佳解决方案。
已更新 min(s) > L
存在漏洞。这对于重新定义的术语来说已经足够了,但对于原始术语来说却是错我已将此条件更改为max(s) > L
。还在二进制搜索中添加了小于L的段的跳过。
这是完整的更新的代码:
from math import pi,floor
def distribute(angles,n):
s = [angles[i] - angles[i-1] for i in xrange(len(angles))]
s = [ls if ls > 0 else 2*pi+ls for ls in s]
Lmin, Lmax = 0., max(s)
while Lmax - Lmin >1e-9:
L = (Lmax + Lmin)/2
if sum(max(floor(ls/L) -1,0) for ls in s ) < n: Lmax = L
else : Lmin = L
L= Lmin
p = []
for i in xrange(len(s)):
u = floor(s[i]/L) -1
if u <= 0:continue
d = s[i]/(u+1)
for j in xrange(u):
p.append(angles[i-1]+d*(j+1))
return p[:n]
print distribute((0, pi/4),1)
print distribute((-pi,-pi/2,pi/2),2
答案 4 :(得分:1)
你从来没有说过精确测量“均匀间距”是什么。区间大小的完全均方根方差与完全间隔的区间大小或其他东西?
如果你在开始时查看任何特定的开放区间,我相信在该区间内放置 k 点的最佳解决方案将始终均匀地隔开它们。因此,问题减少到选择最小间隔大小的截止点,以获得一定数量的间隙点。完成后,如果你没有足够的分配点,从每个区间从最大到最小丢弃一个点并重复,直到你得到理智的东西。
但是,我不确定选择截止点的最佳方式。答案 5 :(得分:0)
我建议您将问题视为:
或
答案 6 :(得分:0)
One idea, write angles as list (in degrees): [30, 80, 120, 260, 310] Convert to differences: [ 50, 40, 140, 50, 80] Note that we wrap around 310 + 80 (mod 360) = 30, the first angle For each point to be added, split the largest difference: n=1, split 140: [50, 40, 70, 70, 50, 80 ] n=2, split 80: [50, 40, 70, 70, 50, 40, 40] Convert back to angles: [30, 80, 120, 190, 260, 310, 350]
答案 7 :(得分:0)
我有一个名为“condition”的函数,它接受两个参数 - 分子(const)和分母(pass-by-ref)。它要么“增长”要么“缩小”分母的值,直到整数个“分母”适合分子,即分子/分母是整数。
分母是增长还是缩小取决于哪一个会导致分数变小。
将分子设置为2 * pi并将分母设置为接近所需间距的任何类型,并且您应该非常接近均匀分布。
请注意,我还有一个“比较”功能,它在一定容差范围内比较两个双打的相等性。
bool compare( const double num1, const double num2, const double epsilon = 0.0001 )
{
return abs(num1 - num2) < epsilon;
}
然后条件函数是
void condition(const double numerator, double &denominator)
{
double epsilon = 0.01;
double mod = fmod( numerator, denominator );
if( compare(mod, 0) )
return;
if( mod > denominator/2 ) // decide whether to grow or shrink
epsilon *= -1;
int count = 0;
while( !compare( fmod( numerator, denominator ), 0, 0.1) )
{
denominator += epsilon;
count++;
if( count > 10000 ) // a safety net
return;
}
}
希望它有所帮助,我知道这个小算法对我来说非常方便了很多次。
答案 8 :(得分:0)
Starting with array [30, 80, 120, 260, 310] and adding n = 5 angles, the given algorithm (see below) gives [30, 55, 80, 120, 155, 190, 225, 260, 310, 350] with a root mean square of the differences between angles rms(diff) = sqrt[sum(diff * diff)] / n = 11.5974135047, which appears to be optimal for practical purposes.