找出最大的凸多边形的面积


28

给定一个整数坐标列表,找到您可以从列表中构造的最大凸多边形的面积,以便-

  • 每个顶点都在列表中
  • 多边形内不包含列表的任何元素。

例:

(0,0)(8,0)(0,1)(3,1)(7,1)(1,2)(5,2)(9,2)(2,3)(5,3) (7,3)(3,4)(5,5)(11,5)

可视化:

o       o
o  o   o
 o   o   o
  o  o o
   o
     o     o

您可以从中制作的最大凸多边形是这样的:

o     
o  o  
 o   o
  o  o
   o
     o

面积为12。


您可以采用任何合理的格式获取坐标列表,并应输出(以适合您选择的语言的适当方式)最大凸多边形的面积,该面积四舍五入到小数点后不少于2位。

此外,您必须使用某种算法,而不是简单地强行使用所有点子集。为了实现这一点,您的程序必须在一分钟内在现代PC上解决50个顶点的列表。

以字节为单位的最短代码获胜。


您知道最坏情况下的快速算法吗?
xnor

3
如果要对100个顶点强制执行时间限制,则可能应至少包括一个这样的测试用例(理想情况下是多个,例如,其中所有100个顶点都属于解决方案的一部分,其中一个包含99个顶点,一个包含仅10个顶点) 。
马丁·恩德

@MartinBüttner遗憾的是,由于我自己没有有效的实现,因此无法生成此测试用例。问题非常棘手:)
orlp

@xnor可以在此处找到几个示例。
orlp

“四舍五入到小数点后不少于2位”?
DavidC

Answers:


12

Javascript ES6,738个字节

((V,C,L,r,k,n,A,G,F,e,i,j,q)=>p=>{p=p.map((p,i)=>({i:i,x:p[0],y:p[1]}));A=(f,p,a,b,v,i)=>{for(i=p[n],v=V(a,b);i--;)if(f(v,V(a,p[i])))return 1};G=(p,i,a)=>{for(i=p[n]-1,a=C(p[i],p[0]);i--;)a+=C(p[i],p[i+1]);if((a/=2)>r)r=a};F=(p,s,l,f,a,b,v)=>(l=s[n],f=s[0],a=s[l-2],b=s[l-1],e[a.i][b.i]||A((a,b)=>C(a,b)?0:a.x<0==b.x<0&&a.y<0==b.y<0&&L(a)>L(b),p,a,b)?0:(p=(v=V(a,b),p[k](x=>C(v,V(a,x))>=0)),A((a,b)=>C(a,b)>0,p,b,f)?0:(p.map(q=>F(p[k](r=>q!==r),[...s,q])),s[2]&&!p[n]&&!e[b.i][f.i]?G(s):0)));e=p.map(x=>p.map(y=>x===y));for(i=p[n];i--;){for(j=i;j--;){q=p[k]((p,x)=>x-i&&x-j);F(q,[p[i],p[j]]);F(q,[p[j],p[i]]);e[i][j]=e[j][i]=1}}console.log(r)})((a,b)=>({x:b.x-a.x,y:b.y-a.y}),(a,b)=>a.x*b.y-a.y*b.x,v=>v.x*v.x+v.y*v.y,0,'filter','length')

这是ES5或更低版本,可以在大多数浏览器和节点中正常运行,而无需进行调整:827字节

eval("(%V,C,L,r,k,n,A,G,F,e,i,j,q){@%p){p=p.map(%p,i){@{i:i,x:p[0],y:p[1]}});A=%f,p,a,b,v,i){for(i=p[n],v=V(a,b);i--;)if(f(v,V(a,p[i])))@1};G=%p,i,a){for(i=p[n]-1,a=C(p[i],p[0]);i--;)a+=C(p[i],p[i+1]);if((a/=2)>r)r=a};F=%p,s,l,f,a,b,v){@(l=s[n],f=s[0],a=s[l-2],b=s[l-1],e[a.i][b.i]||A(%a,b){@C(a,b)!=0?0:a.x<0==b.x<0&&a.y<0==b.y<0&&L(a)>L(b)},p,a,b)?0:(p=(v=V(a,b),p[k](%x){@C(v,V(a,x))>=0})),A(%a,b){@C(a,b)>0},p,b,f)?0:(p.forEach(%q){@F(p[k](%r){@q!==r}),s.concat([q]))}),s[2]&&p[n]==0&&!e[b.i][f.i]?G(s):0)))};e=p.map(%x,i){@p.map(%y,j){@i==j})});for(i=p[n];i--;){for(j=i;j--;){q=p[k](%p,x){@x!=i&&x!=j});F(q,[p[i],p[j]]);F(q,[p[j],p[i]]);e[i][j]=e[j][i]=1}}console.log(r)}})(%a,b){@{x:b.x-a.x,y:b.y-a.y}},%a,b){@a.x*b.y-a.y*b.x},%v){@v.x*v.x+v.y*v.y},0,'filter','length')".replace(/%/g,'function(').replace(/@/g,'return '))

代码返回一个匿名函数。作为参数,它需要一个点数组,例如[[0,1],[2,3],[4,5]]。要使用它,您可以放在var f=它前面,或者如果您想从命令行使用它,请添加(process.argv[2].replace(/ /g,'').slice(1,-1).split(')(').map((x)=>x.split(',')))到最后,然后像这样调用它node convpol.js '(1,2)(3,4)(5,6)'

感谢您的挑战!由于没有参考实现,因此我无法证明这是正确的,但是至少对于点列表的排列而言,它是一致的。我几乎认为这没有用,因为带有调试代码的版本(甚至被禁用)随着指数时间的增加太慢了。我还是决定打高尔夫球,很高兴看到它在我的机器上下降到2秒以内得到50分。它可以在1分钟内计算大约130点。

该算法类似于Graham扫描,不同之处在于它必须在各处搜索空的凸包。

说明

这是该算法工作原理的高级概述。该算法的主要目的是搜索不包围点的逆时针凸环。该过程是这样的:

  1. 从一对点以及所有其他点的列表开始。
  2. 如果当前一对点正好穿过列表中的任何点,请停止。
  3. 顺时针过滤掉所有点对,因为它们会使多边形凹陷。
  4. 对于剩下的所有点,请执行以下操作:
    1. 如果从此点到链的第一个点的直线逆时针穿过或将任何点包围,请跳过此点,因为任何多边形都会将其包围。
    2. 将此点添加到链中,从步骤1开始,使用当前链和点列表进行递归。
  5. 如果没有剩余点,并且链中至少有3个点,则这是有效的凸多边形。记住这些多边形的最大面积。

另外,作为优化,我们记录了选中的链的初始对,因此在链中任何地方看到该对后,随后的任何搜索都可以立即停止搜索,因为已经找到了该对的最大多边形。

该算法永远不会找到一个多边形两次,而我已经实验验证了这一点。


2
+1,这是一个了不起的答案。您也许可以用和替换===和,但是我不确定在不了解您的代码的情况下……!====!=
jrich

1
谢谢!那些特殊的===和!==正在比较对象,所以遗憾的是,不是。它曾经用于比较索引,但是即使经过优化,(x,i)=>p.i==i(13个字符)也比x=>p===x(8个字符)长很多。
ricochet1k

2
有你@Lembik一个解释
ricochet1k

1
您似乎击败了链接的SO问题的注释中提到的O(n ^ 3)记录!

1
好吧,我要去的地方我不相信这可能小于O(n ^ 3)。我对算法复杂性非常陌生。
ricochet1k
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.