基于C语言的凸包算法实现

↘锁芯ラ 提交于 2020-08-12 02:46:47

基于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[top1]构成的向量 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 0x<50,0y<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;
}

结果输出

为了使结果更直观,定义了一个输出函数,能够输出凸包顶点坐标,并在二维坐标中,显示点。

运行结果

在这里插入图片描述
运行结果2
在这里插入图片描述

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!