Dados n segmentos de línea (p1, q1), (p2, q2), … (pn, qn), encuentre si los segmentos de línea dados se cruzan entre sí o no.
Ejemplo:
Entrada: {{1, 5}, {4, 5}}, {{2, 5}, {10, 1}}, {{3, 2}, {10, 3}}, {{6, 4} , {9, 4}},{{7, 1}, {8, 1}}
Salida: 2
Explicación: Líneas {{1, 5}, {4, 5}}, {{2, 5}, {10 , 1}} se intersecan, así como {{2, 5}, {10, 1}}, {{3, 2}, {10, 3}}.
Hemos discutido el problema de detectar si dos segmentos de línea dados se cruzan o no . En este post, ampliamos el problema. Aquí tenemos n segmentos de línea y necesitamos averiguar si dos segmentos de línea se intersecan o no.
Algoritmo ingenuo Una solución ingenua para resolver este problema es verificar cada par de líneas y verificar si el par se cruza o no. Podemos comprobar dos segmentos de recta en tiempo O(1) . Por lo tanto, este enfoque toma O(n 2 ).
Algoritmo de barrido de línea: podemos resolver este problema en O (nLogn)tiempo utilizando el algoritmo de barrido de línea. El algoritmo primero ordena los puntos finales a lo largo del eje x de izquierda a derecha, luego pasa una línea vertical a través de todos los puntos de izquierda a derecha y verifica las intersecciones. Los siguientes son pasos detallados.
1) Sean n líneas dadas. Debe haber 2n puntos finales para representar las n líneas. Ordena todos los puntos según las coordenadas x. Mientras clasifica, mantenga una bandera para indicar si este punto es el punto izquierdo de su línea o el punto derecho.
2) Comience desde el punto más a la izquierda. Haga lo siguiente para cada punto
… a) Si el punto actual es un punto a la izquierda de su segmento de línea, verifique la intersección de su segmento de línea con los segmentos justo encima y debajo de él. Y agrega su línea a activesegmentos de línea (segmentos de línea para los que se ve el punto final izquierdo, pero aún no se ve el punto final derecho). Tenga en cuenta que consideramos solo aquellos vecinos que todavía están activos.
…. b) Si el punto actual es un punto recto, elimine su segmento de línea de la lista activa y verifique si sus dos vecinos activos (puntos justo arriba y abajo) se cruzan entre sí.
El paso 2 es como pasar una línea vertical desde todos los puntos comenzando desde el punto más a la izquierda hasta el punto más a la derecha. Es por eso que este algoritmo se llama Algoritmo de línea de barrido. La técnica Sweep Line es útil en muchos otros algoritmos geométricos, como calcular el diagrama de Voronoi 2D
. ¿Qué estructuras de datos se deben usar para una implementación eficiente?
En el paso 2, necesitamos almacenar todos los segmentos de línea activos. Necesitamos realizar las siguientes operaciones de manera eficiente: a) Insertar
un nuevo segmento de línea
b) Eliminar un segmento de línea
c) Encontrar el predecesor y el sucesor de acuerdo con los valores de las coordenadas y
Árbol Negro. Con un BST de autoequilibrio, podemos realizar todas las operaciones anteriores en tiempo O (Iniciar sesión).
Además, en el paso 1, en lugar de ordenar, podemos usar la estructura de datos de montón mínimo. Construir un montón mínimo toma tiempo O(n) y cada operación de extracción mínima toma tiempo O(Iniciar sesión) (ver esto ).
Pseudocódigo:
el siguiente pseudocódigo no usa heap. Simplemente ordena la array.
sweepLineIntersection(Points[0..2n-1]): 1. Sort Points[] from left to right (according to x coordinate) 2. Create an empty Self-Balancing BST T. It will contain all active line Segments ordered by y coordinate. // Process all 2n points 3. for i = 0 to 2n-1 // If this point is left end of its line if (Points[i].isLeft) T.insert(Points[i].line()) // Insert into the tree // Check if this points intersects with its predecessor and successor if ( doIntersect(Points[i].line(), T.pred(Points[i].line()) ) return true if ( doIntersect(Points[i].line(), T.succ(Points[i].line()) ) return true else // If it's a right end of its line // Check if its predecessor and successor intersect with each other if ( doIntersect(T.pred(Points[i].line(), T.succ(Points[i].line())) return true T.delete(Points[i].line()) // Delete from tree 4. return False
Ejemplo:
Consideremos el siguiente ejemplo tomado de aquí . Hay 5 segmentos de recta , , y . Las líneas verdes punteadas muestran líneas de barrido.
Los siguientes son los pasos seguidos por el algoritmo. Todos los puntos de izquierda a derecha se procesan uno por uno. Mantenemos un árbol de búsqueda binaria autoequilibrado.
1 se inserta en el árbol. El árbol contiene. Sin intersección.
La intersección de y está marcada. se inserta en el árbol. Intersección de 1 y 2 encontrado («Tenga en cuenta que el pseudocódigo anterior regresa en este punto»). Podemos continuar desde aquí para informar todos los puntos de intersección. El árbol contiene , .
La intersección de con está marcada. Sin intersección. se inserta en el árbol. El árbol contiene , , .
se elimina del árbol. La intersección de y está marcada. Se informa de la intersección de y. El árbol contiene,
Las intersecciones de línea con líneas y están marcadas. Sin intersección. se inserta en el árbol. El árbol contiene , , .
La intersección de con está marcada. Sin intersección. se inserta en el árbol. El árbol contiene.
se elimina del árbol. El árbol contiene , , .
se elimina del árbol. El árbol contiene , , . La intersección de con está marcada. Se informa la intersección de with (Tenga en cuenta que la intersección de 2 y 3 se informa nuevamente. Podemos agregar algo de lógica para verificar si hay duplicados). El árbol contiene , .
Ambos se eliminan del árbol y el árbol se vacía.
C++14
#include <bits/stdc++.h> using namespace std; // A point in 2D plane struct Point { int x, y; }; // A line segment with left as Point // with smaller x value and right with // larger x value. struct Segment { Point left, right; }; // An event for sweep line algorithm // An event has a point, the position // of point (whether left or right) and // index of point in the original input // array of segments. struct Event { int x, y; bool isLeft; int index; Event(int x, int y, bool l, int i) : x(x), y(y), isLeft(l), index(i) {} // This is for maintaining the order in set. bool operator<(const Event& e) const { if(y==e.y)return x<e.x; return y < e.y; } }; // Given three collinear points p, q, r, the function checks if // point q lies on line segment 'pr' bool onSegment(Point p, Point q, Point r) { if (q.x <= max(p.x, r.x) && q.x >= min(p.x, r.x) && q.y <= max(p.y, r.y) && q.y >= min(p.y, r.y)) return true; return false; } // To find orientation of ordered triplet (p, q, r). // The function returns following values // 0 --> p, q and r are collinear // 1 --> Clockwise // 2 --> Counterclockwise int orientation(Point p, Point q, Point r) { // See https://www.geeksforgeeks.org/orientation-3-ordered-points/ // for details of below formula. int val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); if (val == 0) return 0; // collinear return (val > 0)? 1: 2; // clock or counterclock wise } // The main function that returns true if line segment 'p1q1' // and 'p2q2' intersect. bool doIntersect(Segment s1, Segment s2) { Point p1 = s1.left, q1 = s1.right, p2 = s2.left, q2 = s2.right; // Find the four orientations needed for general and // special cases int o1 = orientation(p1, q1, p2); int o2 = orientation(p1, q1, q2); int o3 = orientation(p2, q2, p1); int o4 = orientation(p2, q2, q1); // General case if (o1 != o2 && o3 != o4) return true; // Special Cases // p1, q1 and p2 are collinear and p2 lies on segment p1q1 if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on segment p1q1 if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p2, q2 and p1 are collinear and p1 lies on segment p2q2 if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on segment p2q2 if (o4 == 0 && onSegment(p2, q1, q2)) return true; return false; // Doesn't fall in any of the above cases } // Find predecessor of iterator in s. set<Event>::iterator pred(set<Event> &s, set<Event>::iterator it) { return it == s.begin() ? s.end() : --it; } // Find successor of iterator in s. set<Event>::iterator succ(set<Event> &s, set<Event>::iterator it) { return ++it; } // Returns true if any two lines intersect. int isIntersect(Segment arr[], int n) { unordered_map<string,int> mp; // to note the pair for which intersection is checked already // Pushing all points to a vector of events vector<Event> e; for (int i = 0; i < n; ++i) { e.push_back(Event(arr[i].left.x, arr[i].left.y, true, i)); e.push_back(Event(arr[i].right.x, arr[i].right.y, false, i)); } // Sorting all events according to x coordinate. sort(e.begin(), e.end(), [](Event &e1, Event &e2) {return e1.x < e2.x;}); // For storing active segments. set<Event> s; int ans=0; // Traversing through sorted points for (int i=0; i<2*n; i++) { Event curr = e[i]; int index = curr.index; // If current point is left of its segment if (curr.isLeft) { // Get above and below points auto next = s.lower_bound(curr); auto prev = pred(s, next); // Check if current point intersects with // any of its adjacent bool flag=false; if (next != s.end() && doIntersect(arr[next->index], arr[index])){ string s=to_string(next->index+1)+" "+to_string(index+1); if(mp.count(s)==0){mp[s]++;ans++;} //if not already checked we can increase count in map } if (prev != s.end() && doIntersect(arr[prev->index], arr[index])){ string s=to_string(prev->index+1)+" "+to_string(index+1); if(mp.count(s)==0){mp[s]++;ans++;} //if not already checked we can increase count in map } // if same line segment is there then decrease answer as it got increased twice if(prev != s.end() && next != s.end() && next->index==prev->index)ans--; // Insert current point (or event) s.insert(curr); } // If current point is right of its segment else { // Find the iterator auto it=s.find(Event(arr[index].left.x, arr[index].left.y, true, index)); // Find above and below points auto next = succ(s, it); auto prev = pred(s, it); // If above and below point intersect if (next != s.end() && prev != s.end()) { string s=to_string(next->index+1)+" "+to_string(prev->index+1); string s1=to_string(prev->index+1)+" "+to_string(next->index+1); if (mp.count(s)==0&&mp.count(s1)==0&&doIntersect(arr[prev->index], arr[next->index])) ans++; mp[s]++; } // Remove current segment s.erase(it); } } //print pair of lines having intersection for(auto &pr:mp){ cout<<"Line: "<<pr.first<<"\n"; } return ans; } // Driver code int main() { Segment arr[] = { {{1, 5}, {4, 5}}, {{2, 5}, {10, 1}},{{3, 2}, {10, 3}},{{6, 4}, {9, 4}},{{7, 1}, {8, 1}}}; int n = sizeof(arr)/sizeof(arr[0]); cout<<"Number of intersection points: "<<isIntersect(arr, n); return 0; }
Line: 2 3 Line: 1 2 Number of intersection points: 2
Complejidad del tiempo: el primer paso es la clasificación, que requiere tiempo O (nLogn). El segundo paso procesa 2n puntos y para procesar cada punto, toma tiempo O (Iniciar sesión). Por lo tanto, la complejidad temporal general es O(nLogn)
Referencias:
http://www.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf
http://courses.csail.mit.edu/ 6.006/spring11/lectures/lec24.pdf
http://www.youtube.com/watch?v=dePDHVovJlE
http://www.eecs.wsu.edu/~cook/aa/lectures/l25/node10.html
Escriba comentarios si encuentra algo incorrecto, o si desea compartir más información sobre el tema tratado anteriormente.
Publicación traducida automáticamente
Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA