我在matplotlib中绘制数据,用户可以通过套索进行交互,套索内部表示为构成多边形链的顶点列表。我想要做的是修剪套索,如这个高度专业的图片所示:
形式上,我在欧几里德平面上有一个顶点列表,我想在列表中找到两个不相邻的顶点,以便
它们在x和y方向上的差异都低于某个阈值(我想的是在我正在套索的图中可见的轴范围的1%)
列表中位于它们之间的顶点数量最大化
如果不存在这样的对,则套索将被完全丢弃。
天真的方法是O(n^2)
,其中n
是列表的长度:对于每个顶点数1 ≤ i ≤ n - 2
,请确切地检查每对顶点i
索引,对于存在合适对的最高i
,选择找到的第一对。
这甚至可以容忍复杂性,因为一些基本测试表明套索很少超过500个顶点,并且可能完全排除超过大约1000个顶点。不过,在我看来,我应该能做得比这更好。
是否存在小于O(n ^ 2)的算法并找到所需对的索引(如果存在)?
答案 0 :(得分:1)
您可以测试线路交叉点
这是O(n^2)
如果你将线条细分为细分,那么复杂性变得更好看
对于更复杂的这种方法和其他方法,然后O(n^2)
您可以利用循环周期
闭环类似于圆圈,因此x
轴经过两次相同的点,y
轴也是如此。所以请记住,开始x,y
循环遍历所有行,当x
或y
靠近起点时(只有单轴而不是最先出的一个展位)然后停止。
这是O(n)
现在只测试您从起点到某个阈值距离停止和前进的线周围的交点。如果没有找到交叉点再次与另一个轴停止点一起执行此操作。测试仍为O(平方公尺),但这次是m<<n
您可以整合角度
计算中心作为平均点O(n)
现在循环开始和计算角度的线,它在套索圆(从中心)覆盖,将其添加到某个变量,并在达到360度时停止。从最后做同样的事。
现在你选择了循环的内部部分,所以只测试从停止点到套索的开始/停止的行
[edit1]今天有点时间/心情,所以这里有更详细的子弹2
计算套索的边界框
需要min,max
个x,y
坐标让我们调用x0<=x1,y0<=y1
这可以通过O(n)
为每个坐标分配和计算行计数器
如果浮动坐标截断为某个网格/比例,则需要2个数组int cntx[x1-x0+1],cnty[y1-y0+1]
。不要忘记将值清零。对于套索的任何渲染pixel(x,y)
,计算都很容易。
增量计数器cntx[x-x0]++; cnty[y-y0]++;
不会在端点上递增(以避免重复增量)。这也是O(n)
从头开始找到每个坐标超过2行的第一行
让它为i0
找到行数较少或相等的下一行,然后找到每个坐标2行
让它为i1
从结尾处执行相同操作并调用索引j0,j1
找到交叉点
现在您需要检查来自<i0,i1>
的任意行与来自<j0,j1>
的任何行之间的交叉点O(n^2)
这是一个例子:
我手绘的套索 svg 编辑器(遗憾的是 SO 不支持 svg 图片)。在左侧,您会看到蓝线(行数&gt; 2),在侧面有
cntx,cnty
内容的图表。右侧是红色的计算选择(i0,i1),(j0,j1)
C ++中的一些源代码:
int i,i0,i1,j0,j1;
int a,x,y,x0,y0,x1,y1;
int *cntx,*cnty;
List<int> pnt; // laso points (x,y,flag)
// bounding box O(n) and reset flags
x=pnt[0]; x0=x; x1=x;
y=pnt[1]; y0=y; y1=y;
for (i=0;i<pnt.num;)
{
x=pnt[i]; i++;
y=pnt[i]; i++;
pnt[i]=0; i++;
if (x0>x) x0=x;
if (x1<x) x1=x;
if (y0>y) y0=y;
if (y1<y) y1=y;
}
// allocate and compute counter buffers (count how many line at x or y coordinate)
cntx=new int[x1-x0+1];
cnty=new int[y1-y0+1];
for (x=x0;x<=x1;x++) cntx[x-x0]=0;
for (y=y0;y<=y1;y++) cnty[y-y0]=0;
x=pnt[0];
y=pnt[1];
for (i=0;i<pnt.num;)
{
a=pnt[i]; i++;
for (;x<a;x++) cntx[x-x0]++;
for (;x>a;x--) cntx[x-x0]++; x=a;
a=pnt[i]; i++; i++;
for (;y<a;y++) cnty[y-y0]++;
for (;y>a;y--) cnty[y-y0]++; y=a;
}
// select sections with 3 lines (trimable)
for (i=0;i<pnt.num;)
{
x=pnt[i]; i++;
y=pnt[i]; i++;
if ((cntx[x-x0]>2)||(cnty[y-y0]>2)) pnt[i]=1; i++;
}
// select trim areas from start (i0,i1) and from end (j0,j1)
for (i=0 ;i<pnt.num;) { i0=i; i++; i++; a=pnt[i]; i++; if ( a) break; }
for ( ;i<pnt.num;) { i1=i; i++; i++; a=pnt[i]; i++; if (!a) break; }
for (i=pnt.num;i>0 ;) { i--; a=pnt[i]; i--; i--; j1=i; if ( a) break; }
for ( ;i>0 ;) { i--; a=pnt[i]; i--; i--; j0=i; if (!a) break; }
delete[] cntx;
delete[] cnty;
pnt[pnt.num]
是我的动态数组,包含每个顶点的laso坐标和标志 pnt[0]=x(0),pnt[1]=y(0),pnt[2]=flag(0),pnt[3]=x(1),...
在i0,i1,j0,j1
的末尾包含可选择的点/线选择,因此它是至少存在3条平行线的部分,并且从开始和结束的第一条线中选择。希望现在已经足够清楚了。