Convex Hull: Graham Scan Algorithm Explained with Code
Learn the Graham Scan algorithm for computing the convex hull of a set of points.
20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.
- ✓Basic understanding of arrays and sorting
- ✓Familiarity with stack data structure
- ✓Knowledge of cross product and geometry basics
- Convex hull is the smallest convex polygon containing all points.
- Graham Scan sorts points by polar angle and uses a stack to build the hull.
- Time complexity: O(n log n) due to sorting.
- Handles collinear points by keeping only the farthest.
- Common in collision detection, image processing, and GIS.
Imagine you have a bunch of nails hammered into a board. The convex hull is like stretching a rubber band around all the nails; the band touches only the outermost nails and forms a convex shape. Graham Scan is a way to find which nails the band touches, in order, by starting from the lowest nail and then scanning around in order of angle.
Have you ever needed to find the boundary of a set of points? Whether you're building a game with collision detection, analyzing geographic data, or simplifying shapes in computer graphics, the convex hull is a fundamental concept. The convex hull of a set of points is the smallest convex polygon that contains all the points. Think of it as the shape formed by a rubber band stretched around the outermost points.
Among the many algorithms to compute the convex hull, the Graham Scan is one of the most elegant and efficient. It runs in O(n log n) time, thanks to an initial sorting step, and uses a simple stack-based approach to build the hull. In this tutorial, you'll learn how the Graham Scan works, step by step, with clear Python code. We'll also cover common pitfalls, debugging strategies, and real-world applications. By the end, you'll be able to implement the algorithm confidently and understand when to use it.
What is the Convex Hull?
The convex hull of a set of points is the smallest convex polygon that contains all the points. In other words, it's the shape you get if you wrap a rubber band around the outermost points. The convex hull is a fundamental concept in computational geometry with applications in pattern recognition, image processing, statistics (e.g., the convex hull of a data set), and game development (e.g., collision detection).
Formally, a set S is convex if for any two points in S, the line segment connecting them lies entirely in S. The convex hull of a set of points P is the intersection of all convex sets containing P. For a finite set of points, the convex hull is a convex polygon whose vertices are a subset of the original points.
Graham Scan Algorithm Overview
The Graham Scan algorithm computes the convex hull of a set of points in O(n log n) time. It works in three main steps:
- Find the point with the lowest y-coordinate (and leftmost if ties). This point is guaranteed to be on the hull.
- Sort all other points by polar angle relative to this base point. If two points have the same angle, keep only the farthest one (since the closer one is inside the hull edge).
- Build the hull using a stack: Iterate through the sorted points. For each point, while the last two points on the stack and the new point make a non-left turn (i.e., clockwise or collinear), pop the stack. Then push the new point.
The algorithm uses the orientation test (cross product) to determine the turn direction. A left turn means the point is part of the hull; a right turn means the previous point is not on the hull.
Implementing the Orientation Function
The orientation function is the heart of the Graham Scan. It determines whether three points make a left turn, right turn, or are collinear. Given points p, q, r, the cross product (q - p) × (r - p) gives: - Positive: counter-clockwise (left turn) - Negative: clockwise (right turn) - Zero: collinear
In Python, we can implement this as:
``python def orientation(p, q, r): return (q[0] - p[0]) (r[1] - p[1]) - (q[1] - p[1]) (r[0] - p[0]) ``
This function is used in the while loop to pop points that are not part of the convex hull. When orientation is <= 0, we pop because we want to keep only left turns (counter-clockwise). If we want to include collinear points on the hull, we can change the condition to < 0 (strict left turn) and handle collinear points separately.
Handling Collinear Points
Collinear points on the hull boundary are tricky. If multiple points lie on the same line segment that is part of the hull, we only want the endpoints. The standard Graham Scan pops points when orientation <= 0, which removes collinear points. However, if we want to include all collinear points on the hull (e.g., for a more detailed boundary), we need to adjust.
One approach: When sorting, if two points have the same angle relative to the start, keep only the farthest one. Then, during the stack building, use orientation < 0 (strict left turn) to pop, and handle collinear points by keeping them if they are farther from the start. Alternatively, you can first compute the hull with strict left turns, then add collinear points that lie on the hull edges.
In practice, for most applications, you want the minimal convex hull, so you can safely remove collinear points. But if you need all boundary points, you must modify the algorithm.
Complete Python Implementation
Here is a complete, production-ready implementation of the Graham Scan algorithm in Python. It includes error handling for edge cases and uses an epsilon for floating-point comparisons.
```python import math
def orientation(p, q, r, eps=1e-9): val = (q[0] - p[0]) (r[1] - p[1]) - (q[1] - p[1]) (r[0] - p[0]) if abs(val) < eps: return 0 return 1 if val > 0 else -1
def graham_scan(points): if len(points) < 3: return points[:] # Not enough points for a polygon # Find the point with the lowest y (and leftmost if tie) start = min(points, key=lambda p: (p[1], p[0])) # Sort by polar angle and distance def key_func(p): angle = math.atan2(p[1] - start[1], p[0] - start[0]) dist = (p[0] - start[0])2 + (p[1] - start[1])2 return (angle, dist) sorted_pts = sorted(points, key=key_func) # Remove duplicates of same angle (keep farthest) unique = [] for p in sorted_pts: if unique and orientation(start, unique[-1], p) == 0: # Same angle: keep the one farther from start if (p[0]-start[0])2 + (p[1]-start[1])2 > (unique[-1][0]-start[0])2 + (unique[-1][1]-start[1])2: unique[-1] = p else: unique.append(p) # Build hull using stack stack = [] for p in unique: while len(stack) >= 2 and orientation(stack[-2], stack[-1], p) <= 0: stack.pop() stack.append(p) return stack ```
This implementation returns the convex hull points in counter-clockwise order, starting from the lowest point.
Testing and Validation
To ensure your Graham Scan implementation is correct, test with various cases:
- Simple square: Points at (0,0), (0,1), (1,1), (1,0) should return all four.
- Triangle with interior point: Points (0,0), (1,0), (0,1), (0.2,0.2) should return the triangle.
- Collinear points: Points (0,0), (1,1), (2,2), (3,3) should return the endpoints (0,0) and (3,3).
- Duplicate points: Should be handled gracefully (e.g., by removing duplicates first).
- All points collinear: Should return the two farthest points.
- Single point: Should return that point.
- Two points: Should return both.
You can also validate by checking that all original points are inside or on the hull. Use a point-in-polygon test for verification.
Real-World Applications
Convex hull algorithms are used in many fields:
- Collision Detection: In games and robotics, convex hulls approximate object shapes for fast collision checks.
- Image Processing: Hand gesture recognition uses convex hull of hand contour to detect fingers.
- Geographic Information Systems (GIS): Computing the boundary of a set of points (e.g., city limits).
- Statistics: The convex hull of a dataset can be used to identify outliers.
- Pattern Recognition: In machine learning, convex hulls help in support vector machines (SVMs) for finding support vectors.
For example, in a game, you might compute the convex hull of a player's character to detect collisions with obstacles. The hull simplifies the character's shape into a convex polygon, making collision checks faster.
The Case of the Missing Polygon: A GIS Outage
- Always test with collinear points, especially on the hull boundary.
- Use robust orientation functions that handle floating-point precision.
- Validate output by checking that all points are inside or on the hull.
- Consider edge cases like duplicate points or all points collinear.
- Write unit tests that include real-world data with noise.
print(orientation(p, q, r))Check if epsilon is used| File | Command / Code | Purpose |
|---|---|---|
| convex_hull_intro.py | points = [(0,0), (1,1), (2,2), (2,0), (1,0), (0,2)] | What is the Convex Hull? |
| graham_scan_overview.py | def orientation(p, q, r): | Graham Scan Algorithm Overview |
| orientation.py | def orientation(p, q, r): | Implementing the Orientation Function |
| collinear_handling.py | def graham_scan_collinear(points): | Handling Collinear Points |
| graham_scan_complete.py | def orientation(p, q, r, eps=1e-9): | Complete Python Implementation |
| test_graham_scan.py | def test_graham_scan(): | Testing and Validation |
| application_example.py | def check_collision(hull1, hull2): | Real-World Applications |
Key takeaways
Interview Questions on This Topic
Explain the Graham Scan algorithm for computing the convex hull.
Frequently Asked Questions
20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.
That's Geometry. Mark it forged?
4 min read · try the examples if you haven't