Encuentra el área del polígono convexo más grande

29

Dada una lista de coordenadas enteras, encuentre el área del polígono convexo más grande que puede construir de la lista de tal manera que:

  • cada vértice está en la lista
  • Ningún elemento de la lista está contenido dentro del polígono.

Ejemplo:

(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)

Visualizado:

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

El polígono convexo más grande que puede hacer de esto es este:

o     
o  o  
 o   o
  o  o
   o
     o

Con una superficie de 12.


Puede tomar la lista de coordenadas en cualquier formato razonable y debe generar (de manera apropiada para el idioma que elija) el área del polígono convexo más grande, redondeada a no menos de 2 dígitos después del punto decimal.

Además, debe emplear algún tipo de algoritmo, y no simplemente fuerza bruta en todos los subconjuntos de puntos. Para hacer cumplir esto, su programa debe resolver una lista de 50 vértices en menos de un minuto en una PC moderna.

El código más corto en bytes gana.

orlp
fuente
¿Conoces un algoritmo rápido para el peor de los casos?
xnor
3
Si desea imponer un límite de tiempo en 100 vértices, probablemente debería incluir al menos uno de estos casos de prueba (idealmente varios, por ejemplo, uno donde los 100 vértices son parte de la solución, uno donde están 99 y uno donde solo 10 son) .
Martin Ender
@ MartinBüttner Lamentablemente, no puedo generar este caso de prueba, ya que no tengo una implementación que funcione. El problema es bastante complicado :)
orlp
@xnor Un par de ejemplos se pueden encontrar aquí .
orlp
¿"redondeado a no menos de 2 dígitos después del punto decimal"?
DavidC

Respuestas:

12

Javascript ES6, 738 bytes

((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')

Aquí hay una versión ES5 o menos que debería funcionar en la mayoría de los navegadores y nodos sin ajustar: 827 bytes

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 '))

El código devuelve una función anónima. Como parámetro, toma una serie de puntos, como [[0,1],[2,3],[4,5]]. Para usarlo, puede colocarlo var f=antes, o si desea usarlo desde la línea de comando, agregar (process.argv[2].replace(/ /g,'').slice(1,-1).split(')(').map((x)=>x.split(',')))al final y llamarlo comonode convpol.js '(1,2)(3,4)(5,6)'

Gracias por el reto! Como no hay implementación de referencia, no puedo probar que esto sea correcto, pero es consistente al menos para las permutaciones de la lista de puntos. Casi no pensé que esto iba a funcionar, ya que las versiones con código de depuración, incluso deshabilitadas, eran demasiado lentas con un aumento exponencial del tiempo. Decidí jugar golf de todos modos, y me complació ver que se redujo a menos de 2 segundos por 50 puntos en mi máquina. Puede calcular aproximadamente 130 puntos en 1 minuto.

El algoritmo es similar al escaneo de Graham , excepto que tiene que buscar cascos convexos vacíos en todas partes.

Explicación

Aquí hay una descripción general de alto nivel de cómo funciona el algoritmo. La esencia de este algoritmo es solo buscar bucles convexos en sentido antihorario que no encierren un punto. El procedimiento es algo como esto:

  1. Comience con un par de puntos y una lista de todos los demás puntos.
  2. Si el par de puntos actual atraviesa exactamente cualquier punto de la lista, deténgase.
  3. Filtre todos los puntos en el sentido de las agujas del reloj del par actual, ya que harían cóncavo al polígono.
  4. Para todos los puntos restantes, haga lo siguiente:
    1. Si una línea desde este punto hasta el primer punto de la cadena atraviesa o encierra algún punto en sentido antihorario, omita este punto, ya que cualquier polígono encerraría el punto.
    2. Agregue este punto a la cadena, repita desde el paso 1 con la cadena actual y la lista de puntos.
  5. Si no quedaban puntos, y la cadena tiene al menos 3 puntos, este es un polígono convexo válido. Recuerde el área más grande de estos polígonos.

Además, como optimización, registramos el par inicial de la cadena como verificado, por lo que cualquier búsqueda posterior al ver este par en cualquier parte de la cadena puede dejar de buscar inmediatamente, ya que el polígono más grande con este par ya se ha encontrado.

Este algoritmo nunca debería encontrar un polígono dos veces, y lo he verificado experimentalmente.

ricochet1k
fuente
2
+1, esta es una respuesta increíble. Es posible que pueda reemplazar ===y !==con ==y !=, pero no podría estar seguro sin comprender su código ...
jrich
1
¡Gracias! Esos particulares === y! == están comparando objetos, así que no, tristemente. Solía ​​comparar índices, pero (x,i)=>p.i==i(13 caracteres) es bastante más largo que x=>p===x(8 caracteres) incluso después de la optimización.
ricochet1k
2
Hay una explicación para ti @Lembik
ricochet1k
1
¡Parece haber batido el registro O (n ^ 3) mencionado en los comentarios de la pregunta SO vinculada!
1
Bien, estoy llegando a donde no creo que sea posible que esto se ejecute en menos de O (n ^ 3). Soy muy nuevo en la complejidad algorítmica.
ricochet1k