“几何学是描绘宇宙秩序的永恒诗篇。” —— 约翰内斯·开普勒
计算几何将数学的优雅与算法的实用性完美结合,在计算机图形学、机器人导航和地理信息系统中扮演着关键角色。本章将带您探索几何问题的算法解决方案,从基础的点线关系到复杂的空间剖分,揭示算法如何理解和操纵我们的几何世界。
在计算几何中,我们使用简洁的数学结构表示几何对象:
// 二维点
typedef struct {
double x;
double y;
} Point;
// 线段
typedef struct {
Point start;
Point end;
} Segment;
// 多边形
typedef struct {
int num_vertices;
Point *vertices; // 顶点数组
} Polygon;
// 点积:a·b = |a||b|cosθ
double dot_product(Point a, Point b) {
return a.x * b.x + a.y * b.y;
}
// 叉积:a×b = |a||b|sinθ
double cross_product(Point a, Point b) {
return a.x * b.y - a.y * b.x;
}
// 判断点c在ab线段的哪一侧
int direction(Point a, Point b, Point c) {
double cross = cross_product((Point){b.x - a.x, b.y - a.y},
(Point){c.x - a.x, c.y - a.y});
if (cross > 0) return 1; // 逆时针方向(左侧)
if (cross < 0) return -1; // 顺时针方向(右侧)
return 0; // 共线
}
领域 | 应用 | 重要性 |
---|---|---|
计算机视觉 | 物体轮廓识别 | ⭐⭐⭐⭐ |
路径规划 | 机器人导航区域 | ⭐⭐⭐⭐ |
GIS系统 | 地图区域简化 | ⭐⭐⭐⭐ |
碰撞检测 | 快速排斥测试 | ⭐⭐⭐⭐ |
算法步骤:
// 比较函数:按极角排序
int compare_angles(const void *a, const void *b, void *pivot_ptr) {
Point *p0 = pivot_ptr;
Point *p1 = (Point *)a;
Point *p2 = (Point *)b;
double cross = cross_product((Point){p1->x - p0->x, p1->y - p0->y},
(Point){p2->x - p0->x, p2->y - p0->y});
if (cross > 0) return -1;
if (cross < 0) return 1;
// 共线时距离较近者优先
double dist1 = (p1->x - p0->x)*(p1->x - p0->x) + (p1->y - p0->y)*(p1->y - p0->y);
double dist2 = (p2->x - p0->x)*(p2->x - p0->x) + (p2->y - p0->y)*(p2->y - p0->y);
return (dist1 < dist2) ? -1 : 1;
}
// Graham扫描算法
Polygon graham_scan(Point *points, int n) {
// 找到最低点
int min_index = 0;
for (int i = 1; i < n; i++) {
if (points[i].y < points[min_index].y ||
(points[i].y == points[min_index].y && points[i].x < points[min_index].x)) {
min_index = i;
}
}
// 交换最低点到数组首位
Point temp = points[0];
points[0] = points[min_index];
points[min_index] = temp;
// 按极角排序
qsort_r(points + 1, n - 1, sizeof(Point), compare_angles, &points[0]);
// 构建凸包
Point *hull = (Point *)malloc(n * sizeof(Point));
int hull_size = 0;
hull[hull_size++] = points[0];
hull[hull_size++] = points[1];
hull[hull_size++] = points[2];
for (int i = 3; i < n; i++) {
while (direction(hull[hull_size-2], hull[hull_size-1], points[i]) <= 0) {
hull_size--; // 移除非凸点
}
hull[hull_size++] = points[i];
}
Polygon convex_hull;
convex_hull.num_vertices = hull_size;
convex_hull.vertices = hull;
return convex_hull;
}
算法思想:礼品包装法
Polygon jarvis_march(Point *points, int n) {
if (n < 3) return (Polygon){0, NULL};
// 找到最左点
int leftmost = 0;
for (int i = 1; i < n; i++) {
if (points[i].x < points[leftmost].x) {
leftmost = i;
}
}
int *hull_indices = (int *)malloc(n * sizeof(int));
int hull_size = 0;
int current = leftmost;
do {
hull_indices[hull_size++] = current;
int next = (current + 1) % n;
for (int i = 0; i < n; i++) {
if (direction(points[current], points[next], points[i]) < 0) {
next = i;
}
}
current = next;
} while (current != leftmost);
// 创建凸包多边形
Point *hull = (Point *)malloc(hull_size * sizeof(Point));
for (int i = 0; i < hull_size; i++) {
hull[i] = points[hull_indices[i]];
}
free(hull_indices);
Polygon convex_hull;
convex_hull.num_vertices = hull_size;
convex_hull.vertices = hull;
return convex_hull;
}
算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
---|---|---|---|
Graham扫描 | O(n log n) | O(n) | 点集较大 |
Jarvis步进 | O(nh) | O(n) | 凸包点少(h小) |
QuickHull | O(n log n) | O(n) | 平均性能好 |
Chan算法 | O(n log h) | O(n) | 理论最优 |
给定平面上的n个点,找到距离最近的两个点。
// 计算两点距离
double distance(Point a, Point b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx*dx + dy*dy);
}
// 暴力求解(小规模)
double brute_force(Point *points, int n, Point *pair) {
double min_dist = INFINITY;
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
double dist = distance(points[i], points[j]);
if (dist < min_dist) {
min_dist = dist;
pair[0] = points[i];
pair[1] = points[j];
}
}
}
return min_dist;
}
// 分治算法
double closest_pair(Point *points_x, Point *points_y, int n, Point *pair) {
if (n <= 3) {
return brute_force(points_x, n, pair);
}
int mid = n / 2;
Point mid_point = points_x[mid];
// 按y排序的点分成左右两部分
Point *left_y = (Point *)malloc(mid * sizeof(Point));
Point *right_y = (Point *)malloc((n - mid) * sizeof(Point));
int left_idx = 0, right_idx = 0;
for (int i = 0; i < n; i++) {
if (points_y[i].x <= mid_point.x && left_idx < mid) {
left_y[left_idx++] = points_y[i];
} else {
right_y[right_idx++] = points_y[i];
}
}
// 递归求解左右子集
Point left_pair[2], right_pair[2];
double left_dist = closest_pair(points_x, left_y, mid, left_pair);
double right_dist = closest_pair(points_x + mid, right_y, n - mid, right_pair);
double min_dist = left_dist;
if (right_dist < min_dist) {
min_dist = right_dist;
pair[0] = right_pair[0];
pair[1] = right_pair[1];
} else {
pair[0] = left_pair[0];
pair[1] = left_pair[1];
}
// 检查跨分割线点对
Point *strip = (Point *)malloc(n * sizeof(Point));
int strip_size = 0;
for (int i = 0; i < n; i++) {
if (fabs(points_y[i].x - mid_point.x) < min_dist) {
strip[strip_size++] = points_y[i];
}
}
// 检查带状区域内点对
for (int i = 0; i < strip_size; i++) {
for (int j = i+1; j < strip_size && (strip[j].y - strip[i].y) < min_dist; j++) {
double dist = distance(strip[i], strip[j]);
if (dist < min_dist) {
min_dist = dist;
pair[0] = strip[i];
pair[1] = strip[j];
}
}
}
free(left_y);
free(right_y);
free(strip);
return min_dist;
}
// 检查点c是否在线段ab上
bool on_segment(Point a, Point b, Point c) {
return (c.x <= fmax(a.x, b.x) &&
(c.x >= fmin(a.x, b.x)) &&
(c.y <= fmax(a.y, b.y)) &&
(c.y >= fmin(a.y, b.y));
}
// 检查两线段是否相交
bool segments_intersect(Point p1, Point p2, Point p3, Point p4) {
int d1 = direction(p3, p4, p1);
int d2 = direction(p3, p4, p2);
int d3 = direction(p1, p2, p3);
int d4 = direction(p1, p2, p4);
// 一般相交情况
if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
return true;
}
// 特殊情况:共线点
if (d1 == 0 && on_segment(p3, p4, p1)) return true;
if (d2 == 0 && on_segment(p3, p4, p2)) return true;
if (d3 == 0 && on_segment(p1, p2, p3)) return true;
if (d4 == 0 && on_segment(p1, p2, p4)) return true;
return false;
}
高效检测多线段相交:
typedef struct {
double x;
int type; // 0:起点, 1:终点, 2:交点
Segment *segment;
} Event;
void sweep_line_intersection(Segment *segments, int n) {
Event *events = (Event *)malloc(2 * n * sizeof(Event));
int event_count = 0;
// 创建事件:起点和终点
for (int i = 0; i < n; i++) {
// 确保起点在终点左侧
if (segments[i].start.x > segments[i].end.x) {
Point temp = segments[i].start;
segments[i].start = segments[i].end;
segments[i].end = temp;
}
events[event_count++] = (Event){segments[i].start.x, 0, &segments[i]};
events[event_count++] = (Event){segments[i].end.x, 1, &segments[i]};
}
// 按x坐标排序事件
qsort(events, event_count, sizeof(Event), event_compare);
// 扫描线状态:当前活跃线段
Segment **active_segments = (Segment **)malloc(n * sizeof(Segment *));
int active_count = 0;
// 处理事件
for (int i = 0; i < event_count; i++) {
Event e = events[i];
if (e.type == 0) { // 线段起点
// 将新线段插入活跃集
active_segments[active_count++] = e.segment;
// 检查新线段与活跃线段的交点
for (int j = 0; j < active_count - 1; j++) {
if (segments_intersect(e.segment->start, e.segment->end,
active_segments[j]->start, active_segments[j]->end)) {
printf("线段 %d 和 %d 相交\n",
e.segment->id, active_segments[j]->id);
}
}
}
else if (e.type == 1) { // 线段终点
// 从活跃集中移除
for (int j = 0; j < active_count; j++) {
if (active_segments[j] == e.segment) {
// 将最后一个元素移到当前位置
active_segments[j] = active_segments[active_count - 1];
active_count--;
break;
}
}
}
}
free(active_segments);
free(events);
}
每个点拥有一个区域,区域内的点距离该点比距离其他点更近。
graph TD
A[点集P] --> B[平面分割]
B --> C[每个点对应一个区域]
C --> D[区域边界是垂直平分线]
D --> E[应用:最近邻查询]
Voronoi图的对偶图,满足:
typedef struct {
Point vertices[3]; // 三角形顶点
Triangle *neighbors[3]; // 相邻三角形
} Triangle;
// 增量算法框架
void delaunay_triangulation(Point *points, int n) {
// 创建超级三角形包含所有点
Triangle super_triangle = create_super_triangle(points, n);
// 初始化三角剖分
TriangleList *triangles = create_triangle_list();
add_triangle(triangles, super_triangle);
// 逐点插入
for (int i = 0; i < n; i++) {
Point p = points[i];
TriangleList *bad_triangles = find_bad_triangles(triangles, p);
Polygon polygon = create_polygon_hole(bad_triangles);
// 移除坏三角形
remove_triangles(triangles, bad_triangles);
// 重新三角剖分
triangulate_polygon(p, polygon, triangles);
}
// 移除超级三角形相关三角形
remove_super_triangle(triangles, super_triangle);
}
结构 | 应用领域 | 优势 |
---|---|---|
Voronoi图 | 路径规划 | 自然分割空间 |
Delaunay三角剖分 | 有限元分析 | 高质量网格生成 |
两者结合 | 地形建模 | 精确表示地形特征 |
无线网络规划 | 优化基站覆盖 |
#include
#include
#include
#include
// [点、线段、多边形定义...]
// [几何运算函数...]
// [Graham扫描实现...]
// [最近点对实现...]
// 生成随机点集
Point* generate_random_points(int n, double min_x, double max_x, double min_y, double max_y) {
Point *points = (Point *)malloc(n * sizeof(Point));
srand(time(NULL));
for (int i = 0; i < n; i++) {
points[i].x = min_x + (double)rand() / RAND_MAX * (max_x - min_x);
points[i].y = min_y + (double)rand() / RAND_MAX * (max_y - min_y);
}
return points;
}
// 可视化点集和凸包
void visualize_points(Point *points, int n, Polygon convex_hull, Point *closest_pair) {
printf("点集可视化:\n");
printf("点数量: %d\n", n);
// 打印点坐标
for (int i = 0; i < n; i++) {
printf("点 %d: (%.2f, %.2f)\n", i, points[i].x, points[i].y);
}
// 打印凸包
printf("\n凸包顶点 (%d 个):\n", convex_hull.num_vertices);
for (int i = 0; i < convex_hull.num_vertices; i++) {
printf("顶点 %d: (%.2f, %.2f)\n", i,
convex_hull.vertices[i].x, convex_hull.vertices[i].y);
}
// 打印最近点对
if (closest_pair) {
printf("\n最近点对:\n");
printf("点1: (%.2f, %.2f)\n", closest_pair[0].x, closest_pair[0].y);
printf("点2: (%.2f, %.2f)\n", closest_pair[1].x, closest_pair[1].y);
printf("距离: %.4f\n", distance(closest_pair[0], closest_pair[1]));
}
}
// 简单ASCII可视化
void ascii_visualization(Point *points, int n, Polygon convex_hull, Point *closest_pair) {
const int width = 50;
const int height = 20;
char grid[height][width];
// 初始化网格
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
grid[y][x] = ' ';
}
}
// 绘制点
for (int i = 0; i < n; i++) {
int x = (int)((points[i].x - 0) / 100 * width);
int y = (int)((points[i].y - 0) / 100 * height);
if (x >= 0 && x < width && y >= 0 && y < height) {
grid[y][x] = '.';
}
}
// 绘制凸包
for (int i = 0; i < convex_hull.num_vertices; i++) {
int x1 = (int)((convex_hull.vertices[i].x - 0) / 100 * width);
int y1 = (int)((convex_hull.vertices[i].y - 0) / 100 * height);
int x2 = (int)((convex_hull.vertices[(i+1)%convex_hull.num_vertices].x - 0) / 100 * width);
int y2 = (int)((convex_hull.vertices[(i+1)%convex_hull.num_vertices].y - 0) / 100 * height);
// 简单线段绘制
int dx = abs(x2 - x1);
int dy = abs(y2 - y1);
int steps = dx > dy ? dx : dy;
for (int s = 0; s <= steps; s++) {
double t = (double)s / steps;
int x = (int)(x1 + t * (x2 - x1));
int y = (int)(y1 + t * (y2 - y1));
if (x >= 0 && x < width && y >= 0 && y < height) {
grid[y][x] = '#';
}
}
}
// 绘制最近点对
if (closest_pair) {
int x1 = (int)((closest_pair[0].x - 0) / 100 * width);
int y1 = (int)((closest_pair[0].y - 0) / 100 * height);
int x2 = (int)((closest_pair[1].x - 0) / 100 * width);
int y2 = (int)((closest_pair[1].y - 0) / 100 * height);
if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) {
grid[y1][x1] = 'A';
}
if (x2 >= 0 && x2 < width && y2 >= 0 && y2 < height) {
grid[y2][x2] = 'B';
}
}
// 打印网格
printf("\nASCII 可视化:\n");
for (int y = height - 1; y >= 0; y--) {
printf("%2d |", y);
for (int x = 0; x < width; x++) {
putchar(grid[y][x]);
}
printf("\n");
}
printf(" ");
for (int x = 0; x < width; x += 5) {
printf("+----");
}
printf("\n ");
for (int x = 0; x < width; x += 5) {
printf("%-5d", x);
}
printf("\n");
}
int main() {
// 生成随机点集
int n = 20;
Point *points = generate_random_points(n, 0, 100, 0, 100);
// 计算凸包
Polygon convex_hull = graham_scan(points, n);
// 计算最近点对
Point closest_pair[2];
Point *points_y = (Point *)malloc(n * sizeof(Point));
memcpy(points_y, points, n * sizeof(Point));
qsort(points, n, sizeof(Point), compare_x);
closest_pair(points, points_y, n, closest_pair);
// 可视化结果
visualize_points(points, n, convex_hull, closest_pair);
ascii_visualization(points, n, convex_hull, closest_pair);
// 清理内存
free(convex_hull.vertices);
free(points);
free(points_y);
return 0;
}
// 多边形碰撞检测
bool polygon_collision(Polygon poly1, Polygon poly2) {
// 分离轴定理(SAT)
for (int i = 0; i < poly1.num_vertices; i++) {
Point edge = (Point){
poly1.vertices[(i+1)%poly1.num_vertices].x - poly1.vertices[i].x,
poly1.vertices[(i+1)%poly1.num_vertices].y - poly1.vertices[i].y
};
// 计算法向量
Point normal = {-edge.y, edge.x};
// 投影多边形1
double min1 = INFINITY, max1 = -INFINITY;
for (int j = 0; j < poly1.num_vertices; j++) {
double projection = dot_product(normal, poly1.vertices[j]);
min1 = fmin(min1, projection);
max1 = fmax(max1, projection);
}
// 投影多边形2
double min2 = INFINITY, max2 = -INFINITY;
for (int j = 0; j < poly2.num_vertices; j++) {
double projection = dot_product(normal, poly2.vertices[j]);
min2 = fmin(min2, projection);
max2 = fmax(max2, projection);
}
// 检查分离轴
if (max1 < min2 || max2 < min1) {
return false;
}
}
return true;
}
// 计算两点间视线是否被阻挡
bool line_of_sight(Point start, Point end, Segment *obstacles, int num_obstacles) {
for (int i = 0; i < num_obstacles; i++) {
if (segments_intersect(start, end,
obstacles[i].start, obstacles[i].end)) {
return false;
}
}
return true;
}
计算几何展示了算法与数学的完美融合:
下一章预告:并行算法
第十九章我们将探索并行算法的世界:
- 并行计算模型:PRAM、BSP、MapReduce
- 并行算法设计模式:分治、流水线、MapReduce
- 经典并行算法:并行排序、并行搜索
- 并行图算法:BFS、最短路径
- GPU并行计算:CUDA/OpenCL基础
随着多核处理器和分布式系统的发展,并行算法已成为解决大规模问题的关键。下一章将揭示如何设计高效并行算法,充分利用现代计算资源。
计算几何不仅是计算机科学的基石,更是连接数学与应用的桥梁。从游戏中的碰撞检测到地图导航的最短路径,从机器人运动规划到有限元分析,几何算法以优雅的数学和高效的实现塑造着我们的数字世界。