基于C语言的凸包算法实现
非计算机专业,代码有些的不好的地方,大佬轻喷^ _ ^
根据要求,需要使用C语言实现凸包算法——Graham扫描法,本文将从算法理解、实现思路、遇到的问题及其解决方案三个方面来阐述实现过程。
算法理解
凸包算法Graham扫描法,在不考虑排序算法的时间复杂度情况下,算法核心程序的时间复杂度为 O ( n l o g n ) O(n log n) O(nlogn),其主要算法思想如下:
首先是预处理过程,获得一组随机点集,选取位于二维空间中左下角的点,即在纵坐标(y)最小情况下横坐标(x)为最小的点 P 0 P_0 P0 。以该点位极坐标原点计算其余各点的极角 θ \theta θ,并根据极角大小进行升序排序,若极角相同则按极径大小按升序排列。由此得到一组按照极角排序的点集 { P 0 , P 1 , . . . , P n } \left\{P_0,P_1,...,P_n\right\} {P0,P1,...,Pn}(如下图所示)。
完成预处理之后即Graham算法的核心步骤,主要通过栈的方式来实现凸包点的计算。首先将 P 0 , P 1 P_0,P_1 P0,P1两点压栈,他们必然属于凸包上的点。然后进入迭代过程,以栈顶元素 A [ t o p ] A[top] A[top]和次栈顶元素 A [ t o p − 1 ] A[top-1] A[top−1]构成的向量 a ⃗ \vec{a} a 为基准计算其与当前 P k P_k Pk点与栈顶元素 A [ t o p ] A[top] A[top]构成的向量 b ⃗ \vec{b} b 的叉积 ,若结果为正(零)则 b ⃗ \vec{b} b 位于 a ⃗ \vec{a} a 的逆时针方向(共线), P k P_k Pk进栈 ,若结果为负则 b ⃗ \vec{b} b 位于 a ⃗ \vec{a} a 的顺时针方向, A [ t o p ] A[top] A[top]出栈, P k P_k Pk进栈,直至扫描至最后一个点,将 P 0 P_0 P0 再次进栈是凸包闭合。
由于要求顺时针输出凸包顶点,则将栈中元素从栈顶向栈底依次输出即可。
实现思路及过程
根据Graham扫描法的算法理解,将程序实现分为了随机点坐标初始化、极角计算及排序、Graham核心算法和结果输出四个模块共计8个函数进行编码实现。
结构体定义
// 点坐标
typedef struct POINT {
int x;
int y;
}Point;
坐标初始化
首先构造存储点坐标的结构体Point,该结构体中仅包含横坐标x和纵坐标y。根据要求需要随机生成100个点,使用宏定义点集大小(SIZE)为100。使用<stdlib.h>库下的rand()函数以当前系统时间为种子生成 0 ≤ x < 50 , 0 ≤ y < 50 0\le x<50,0\le y<50 0≤x<50,0≤y<50 的点,并依次存入大小为SIZE的Point的类型的一维数组中。
void InitPoint(Point* p) {
int i;
srand(time(0));
for (i = 0; i < SIZE; i++) {
(p + i)->x = (int)(rand() % 50);
(p + i)->y = (int)(rand() % 50);
}
}
极角计算及排序
首先选取点集中位于左下角的点,采用的方法为先找出纵坐标 最小的坐标点(集),然后在其中找出横坐标 最小的坐标点,记录该点位于原始点集的位置,将其与第一个点进行交换。接着使用<math.h>库下的atan()函数计算各点的极角,并将其记录在double类型大小为SIZE的一维数组angle中,令极坐标原点的极角: a n g l e [ 0 ] = 0 angle[0] = 0 angle[0]=0。使用冒泡排序算法对极角进行排序,同时改变点集中各点的顺序。在排序是要考虑当极角相同时按极径从小到大排序。
Graham核心算法
根据算法分析结果,定义一个Point类型的一维数组作为栈空间,定义栈顶定位变量top,用于标记栈顶元素在栈中的位置,定义临时变量temp_point用记录当前扫描到的坐标点,定义叉积计算函数,返回值为布尔类型。当叉积为非负时返回true,否则返回false。判断temp_point和栈顶元素,次顶元素三个点组成的两个向量的方向,若叉积返回值为正,则将temp_point进栈,否则将当前栈顶元素出栈,继续判断现在的栈顶元素和次顶元素与temp_point三个点的向量叉积……
得到包含所有凸包顶点的栈数组,最后将 点进栈形成封闭凸包,在形成封闭凸包前需要对 以及当前栈顶和次顶元素进行判断是否符合凸包结构,若符合则将 进栈,反之将当前栈顶元素出栈,重复判断步骤直至符合为止。
int myGraham(Point* p, Point* p_stack) {
int top = -1; //栈顶指针
int p_index = 0; //点索引
Point temp_point;
top++; p_stack[top] = p[0]; p_index++; //push
top++; p_stack[top] = p[1]; p_index++; //push
while (p_index < SIZE) {
temp_point = p[p_index];
if (X(p_stack[top - 1], p_stack[top], temp_point))
{
top++; p_stack[top] = temp_point;//push
}
else {
top--;//pop
continue;
}
p_index++;
}
while (TRUE) {
if (!X(p_stack[top - 1], p_stack[top], p[0])) {
top--;
}
else {
break;
}
}
top++; p_stack[top] = p[0];
return top;
}
结果输出
为了使结果更直观,定义了一个输出函数,能够输出凸包顶点坐标,并在二维坐标中,显示点。
运行结果
来源:oschina
链接:https://my.oschina.net/u/4355012/blog/4274913