(Translated by https://www.hiragana.jp/)
Sweep Line Algorithm - Find if any Two Segments Intersect - GeeksforGeeks
Open In App

Sweep Line Algorithm - Find if any Two Segments Intersect

Last Updated : 23 Jul, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

Given n line segments represented as a 3D vector points[][][], where each line segment i is defined by its endpoints stored in points[i][0] and points[i][1] (each containing 2 integers), your task is to determine if any two of these line segments intersect and find the number of intersection points.

Examples:

Input: points[][][] = [ [ [1, 5], [4, 5] ],
[ [2, 5], [10, 1] ],
[ [3, 2], [10, 3] ],
[ [6, 4], [9, 4] ],
[ [7, 1], [8, 1] ] ]
Output: 2
Explanation: Lines [ [1, 5], [4, 5] ] intersects with [ [2, 5], [10, 1] ] which further intersects with [ [3, 2], [10, 3] ]. Thus there are two intersection points.

[Naive Approach] - Check Every Pair - O(n^2) Time

A naive solution to solve this problem is to check every pair of lines and check if the pair intersects or not. We can check two line segments in O(1) time. Therefore, this approach takes O(n2). 

[Expected Approach] - Sweep Line Algorithm - O(n Log n) Time and O(n) Space

The idea is to use a sweep line method that processes the endpoints of the line segments from left to right, dynamically maintaining a set of segments currently "active" as the sweep line moves. With this approach, we only check for intersections between line segments that are close to each other in the vertical ordering, thus ensuring we handle the problem in a methodical and efficient manner. To manage the active segments, a self-balancing binary search tree (like an AVL or Red-Black Tree) or a similar data structure is used; this allows us to quickly insert new segments, remove segments once their right endpoint is passed, and find the segments immediately above and below a given segment.

Below is given the step-by-step approach to solve the problem:

  • Begin by representing every line segment with its two endpoints and mark each endpoint to indicate whether it is the left or the right end of the segment.
  • Sort all the endpoints according to their x-coordinate values.
  • Initialize an empty self-balancing binary search tree (or min heap for dynamic endpoint processing) to keep track of the active segments along the y-axis.
  • Process each endpoint from left to right:
    • If the current endpoint is a left endpoint, insert its segment into the active set and check for intersections with the immediate neighbors (the segment just above and the one just below) in the active set.
    • If the current endpoint is a right endpoint, remove its segment from the active set and check if the segments that were immediately above and below (now adjacent after removal) intersect with each other.
  • Continue the process until all endpoints have been handled and all potential intersections are checked.

Pseudocode:

function sweepLineIntersection(Points):
    // Sort the points according to their x-coordinate.
    sort(Points)  
    
    // T: self-balancing BST to store active segments ordered by their y-coordinates.
    T = new BST()  
    
    // Iterate over each point in the sorted array.
    for i from 0 to length(Points) - 1:
        currentPoint = Points[i]
        
        if currentPoint.isLeft then:
            // Insert the segment corresponding to the left endpoint.
            segment = currentPoint.segment
            T.insert(segment)
            
            // Retrieve the immediate neighbor segments.
            pred = T.predecessor(segment)
            succ = T.successor(segment)
            
            // Check intersection with the predecessor.
            if pred is not null and doIntersect(segment, pred) then
                return true
            // Check intersection with the successor.
            else if succ is not null and doIntersect(segment, succ) then
                return true
        else:  // currentPoint is the right endpoint of its segment.
            segment = currentPoint.segment
            
            // Before removing the segment, get its neighbors.
            pred = T.predecessor(segment)
            succ = T.successor(segment)
            
            // Remove the segment from the tree.
            T.delete(segment)
            
            // After removal, if both neighbors exist, check if they intersect.
            if pred is not null and succ is not null and doIntersect(pred, succ) then
                return true
                
    // No intersections found.
    return false

Example:

There are 5 line segments 1, 2, 3, 4 and 5 and the dotted green lines show sweep lines. 

sweepline

Following are steps followed by the algorithm

  • Process the left endpoint of segment 1.
    • Insert segment 1 into the BST.
    • The BST now contains: 1
    • No intersection is found.
  • Process the left endpoint of segment 2
    • Check for intersection between segment 2 and segment 1.
    • Insert segment 2 into the BST.
    • Intersection of 1 and 2 is detected. (In the pseudocode, we would return here, but we continue in this example to find all possible intersections.)
    • The BST now contains: 1, 2
  • Process the left endpoint of segment 3
    • Check for intersection between segment 3 and segment 1.
    • No intersection is found.
    • Insert segment 3 into the BST.
    • The BST now contains: 1, 2, 3
  • Process the right endpoint of segment 1
    • Remove segment 1 from the BST.
    • Check for intersection between segments 2 and 3.
    • Intersection of 2 and 3 is found and reported.
    • The BST now contains: 2, 3
  • Process the left endpoint of segment 4
    • Check for intersections between segment 4 and existing segments (2 and 3).
    • No intersection is found.
    • Insert segment 4 into the BST.
    • The BST now contains: 2, 4, 3
  • Process the left endpoint of segment 5
    • Check for intersection between segment 5 and segment 3.
    • No intersection is found.
    • Insert segment 5 into the BST.
    • The BST now contains: 2, 4, 3, 5
  • Process the right endpoint of segment 5
    • Remove segment 5 from the BST.
    • The BST now contains: 2, 4, 3.
  • Process the right endpoint of segment 4
    • Remove segment 4 from the BST.
    • The BST now contains: 2, 3
    • Check for intersection between segments 2 and 3.
    • Intersection of 2 and 3 is reported again. (Additional logic might be added to avoid duplicating the same intersection.)
    • The BST still contains: 2, 3
  • Process the right endpoints of segments 2 and 3
    • Remove segments 2 and 3 from the BST.
    • The BST becomes empty.

After these steps, all endpoints have been processed, and all intersections have been identified (with duplicates possibly reported depending on the implementation details). 

C++
#include <bits/stdc++.h>
using namespace std;

// An event for the sweep line algorithm
class Event {
public:
    int x, y;
    bool isLeft;
    int index;
    Event(int x, int y, bool l, int i) {
        this->x = x;
        this->y = y;
        this->isLeft = l;
        this->index = i;
    }

    // Order events by y-coordinate, then by x-coordinate
    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(vector<int> p, vector<int> q, vector<int> r) {
    if (q[0] <= max(p[0], r[0]) && q[0] >= min(p[0], r[0]) &&
        q[1] <= max(p[1], r[1]) && q[1] >= min(p[1], r[1]))
        return true;
    return false;
}

// To find orientation of ordered triplet (p, q, r).
// Returns 0 if collinear, 1 if clockwise, 2 if counterclockwise.
int orientation(vector<int> p, vector<int> q, vector<int> r) {
    int val = (q[1] - p[1]) * (r[0] - q[0]) -
              (q[0] - p[0]) * (r[1] - q[1]);

     // collinear
    if (val == 0)
        return 0;
    
    // clock- or counterclockwise
    return (val > 0) ? 1 : 2; 
}

// to find if two segments intersect
bool doIntersect(vector<vector<int>> s1, vector<vector<int>> s2) {
    vector<int> p1 = s1[0], q1 = s1[1], p2 = s2[0], q2 = s2[1];

    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
    if (o1 == 0 && onSegment(p1, p2, q1)) return true;
    if (o2 == 0 && onSegment(p1, q2, q1)) return true;
    if (o3 == 0 && onSegment(p2, p1, q2)) return true;
    if (o4 == 0 && onSegment(p2, q1, q2)) return true;

    return false;
}

// Returns the predecessor iterator in set s.
set<Event>::iterator pred(set<Event> &s, set<Event>::iterator it) {
    return it == s.begin() ? s.end() : --it;
}

// Returns the successor iterator in set s.
set<Event>::iterator succ(set<Event> &s, set<Event>::iterator it) {
    return ++it;
}

// Returns the number of intersections found between segments.
int isIntersect(vector<vector<vector<int>>> &lines) {

    // To avoid duplicate intersection reports.
    unordered_map<string, int> mp;  

    vector<Event> events;
    int n = lines.size();
    for (int i = 0; i < n; ++i) {

        // Create events for the left and right endpoints.
        events.push_back(Event(lines[i][0][0], lines[i][0][1], true, i));
        events.push_back(Event(lines[i][1][0], lines[i][1][1], false, i));
    }

    // Sort events according to x-coordinate.
    sort(events.begin(), events.end(), [](Event &e1, Event &e2) {
        return e1.x < e2.x;
    });

    // Set for storing active segments.
    set<Event> active;
    int ans = 0;

    // Process all events.
    for (int i = 0; i < 2 * n; i++) {
        Event curr = events[i];
        int index = curr.index;

        if (curr.isLeft) {

            // For the left endpoint, get neighbors in the active set.
            auto next = active.lower_bound(curr);
            auto prev = pred(active, next);

            // Check for intersection with the next segment.
            if (next != active.end() && doIntersect(lines[next->index], lines[index])) {
                string key = to_string(next->index + 1) + " " + to_string(index + 1);
                if (mp.count(key) == 0) {
                    mp[key]++;
                    ans++;
                }
            }

            // Check for intersection with the previous segment.
            if (prev != active.end() && doIntersect(lines[prev->index], lines[index])) {
                string key = to_string(prev->index + 1) + " " + to_string(index + 1);
                if (mp.count(key) == 0) {
                    mp[key]++;
                    ans++;
                }
            }

            // To avoid counting duplicates if the same segment is both neighbor.
            if (prev != active.end() && next != active.end() && next->index == prev->index)
                ans--;

            active.insert(curr);
        } 
        
        else {

            // For the right endpoint, find the matching left endpoint in the active set.
            auto it = active.find(Event(lines[index][0][0], lines[index][0][1], true, index));
            auto next = succ(active, it);
            auto prev = pred(active, it);

            // If both neighbors exist, check if they intersect.
            if (next != active.end() && prev != active.end()) {
                string key1 = to_string(next->index + 1) + " " + to_string(prev->index + 1);
                string key2 = to_string(prev->index + 1) + " " + to_string(next->index + 1);
                if (mp.count(key1) == 0 && mp.count(key2) == 0 && doIntersect(lines[prev->index], lines[next->index]))
                    ans++;
                mp[key1]++;
            }

            // Remove the segment from the active set.
            active.erase(it);
        }
    }

    // Report all intersecting pairs.
    for (auto &pr : mp) {
        cout << "Line: " << pr.first << "\n";
    }
    return ans;
}

int main() {
    vector<vector<vector<int>>> lines = {
        { {1, 5}, {4, 5} },
        { {2, 5}, {10, 1} },
        { {3, 2}, {10, 3} },
        { {6, 4}, {9, 4} },
        { {7, 1}, {8, 1} }
    };
    cout << isIntersect(lines);
    return 0;
}
Java Python C# JavaScript

Output
Line: 2 3
Line: 1 2
2


Note: The above code does not handle cases when segments share end points (Same starting points or same ending points or same starting and ending points) because we handle all points in the same way without considering the line segment number. We have done that way to keep the code simple. The Event class currently compares events based only on y and x, and does not distinguish between segments with the same start point. To handle the common end points, we need to differentiate events more clearly in the comparator to avoid ambiguity between events with the same coordinates.

Time Complexity: O(n * log n), initially, sorting the points requires O(n log n) time. Next, each point is processed in O(log n) time, so processing all 2n points takes O(2n log n) time. This simplifies to an overall time complexity of O(n log n).
Space Complexity: O(n)


Article Tags :
Practice Tags :

Similar Reads