用c++实现凸包问题、扩展欧几里得算法

6.4.2 凸包问题

【问题】设S={(x1,y1), (x2, y2), ···,(xn, yn)}是平面上n个点构成的集合,凸包问题(convex hull problem)要求找出集合 S 的最小凸多边形


【想法】设p1=(x1,y1),p2=(x2,y2),…,pn=(xn,yn)按照x轴坐标升序排列,则最左边的点p1和最右边的点pn,一定是该集合的凸包极点,如图 6-14所示。设p1pn是经过点p1和pn的直线,这条直线把集合S分成两个子集:S1是位于直线上侧和直线上的点构成的集合,S2是位于直线下侧和直线上的点构成的集合。S1的凸包由下列线段构成:以p1和pn为端点的线段构成的下边界,以及由多条线段构成的上边界,这条上边界称为上包(upper envelope)。类似地,S2中的多条线段构成的下边界称为下包(lower envelope)。整个集合S 的凸包是由上包和下包构成的。由此得到凸包问题的分治策略:
(1)划分:设p1pn是经过最左边的点p1和最右边的点pn的直线,则直线p1pn把集合S 分成两个子集S1和S2。
(2)求解子问题:求集合S1的上包和集合S的下包。
(3)合并解:求解过程中得到凸包的极点,因此,合并步骤无须执行任何操作。
        下面讨论如何求解子问题。对于集合 S1首先找到 S1中距离直线p1pn,最远的点Pmax如图6-15所示。S1中所有在直线p1pmax上侧的点构成集合 S1,1,S1中所有在直线pmaxpn上侧的点构成集合S1,b2,包含在三角形pmaxp1pn之中的点可以不考虑了。递归地继续构造集合S1,1的上包和集合S1,2的上包,然后将求解过程中得到的所有最远距离的点连接起来,就可以得到集合S1的上包。同理,可求得集合S1的下包。

        接下来的问题是如何判断一个点在给定直线的上侧还是下侧,以及如何计算一个点到给定直线的距离。在平面上,经过两个点pi(xi,yi)和pj(xj,yj)的直线方程为:
Ax+By+C=0 (其中,A=yi-yj, B=xj-xi,C=xiyj;-yixj )
        对于点p(x0,y0),如果点p在直线pipj的上侧,则Ax+By+C>0;如果点p在直线pipj的下侧,则Ax+By+C<0,并且点p到直线力pipj的距离为:

【算法】 分治法求解凸包问题的关键是求给定直线的上包和下包,下面给出求直线pipj的上包算法。
算法:求直线pipj的上包UpHull
输人:按x坐标升序排列的n(n>=2)个点的集合S={(xi,xj),(xi+1,yi+1),...,(xj,yj)}

输出:凸包的极点
1.如果n等于2,则输出(xi,yi)和(xj,yj),算法结束;
2. maxd=0;max=i +1;
3.循环变量k从i+1至j-1,依次对集合S的点pk(xk,yk)执行下述操作:
3.1 如果点pk在直线pipj的上侧,则d=该点到直线的距离;
3.2如果(d> maxd),则 maxd=d;max=k;
4.递归求解pipmax的上包和pmaxpj的上包;


【算法分析】 分治法求解门包问题首先要对点集合 S进行排序,设ISI=n,则时间代价是O(nlog2n)。如果每次对集合进行划分都得到两个规模大致相等的子集合,这是最好情况,时间复杂度是O(nlog2n);如果每次划分只得到比上一次划分少一个元素的子集合(另一个子集合为空),这是最坏情况,时间复杂度是O(n^2);平均情况与最好情况同数量级。


 

6.5.1 扩展欧几里得算法


定理6.1 若d能整除a且d能整除,则能整除a和b的任意线性组合ax+by,

定理6.2 令gcd(a,b)表示自然数a和b的最大公约数,则gcd(a, b)是a和b的最小正线性组合。a和b的最小正线性组合是在ax+by的所有值中,值最小的正整数

        定理6.1和6.2的证明略。例如,5能够整除35和25,则对任意x和y,一定有5能整除 35x+25y;由于5是35和25的最大公约数, 则35x+25y的最小正线性组合是5。


【问题】 gcd(a, b)是a和b的最小正线性组合,那么在这个最小正线性组合中,z和y的值是多少?即当x和y取何值时,ax+by=gcd(a,b)。


【想法】 扩展欧几里得算法是用辗转相除法求ax +by=gcd(a,b)的整数解,辗转相除可以用递归实现。表6-1给出了扩展欧几里得算法的执行过程示例,在递归执行过程中,每一次辗转相除后变换ax+by=gcd(a, b)中a和6的值得到等价的线性组合,在达到递归结束条件求得 gcd(a,b)的值后再依次返回,求得每一个等价线性组合的整数解,最终求得使ax+by=gcd(a, b)成立的整数解。注意,这个整数解可能是负整数。

        下面推导等价线性组合解之间的关系。若b=0, 则 gcd(a, b)=a,得到x=1, y=0(实际上y可以取任意值);若b≠0, 则
        ax +by=gcd(a, b)=gcd(b, a mod b)=bx'+(a mod b)y'
整理得
        ax+by=bx'+(a mod b)y'=bx'+[a - (a/b)×b]y'=ay'+b[x’- (a/b)y']

        x=y'且y=x’ -(a/b)y'
以上就是扩展欧几里得算法,也是求二元一次方程的基本方法。
【算法实现】 设递归函数ExGcd实现扩展欧几里得算法,形参a和b表示ax+by=gcd(a, b)的系数,形参x和y 以传引用形式接收求得的整数解,函数的返回值是a和b 的最大公约数,程序如下。

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
#include<math.h> 


int ExGcd(int a, int b, int &x, int &y)
{
int d, temp;
if (0 == b) { x = 1; y = 0; return a; }
d = ExGcd(b, a % b, x, y);
temp = x; x = y; y = temp - a / b * y;
return d;
}
int main( )
{
 int d, a = 35, b = 25, x = 0, y = 0;
    d = ExGcd(a, b, x, y);
    std::cout << "最大公约数为: " << d << std::endl;
    std::cout << "x 的值为: " << x << std::endl;
    std::cout << "y 的值为: " << y << std::endl;
    return 0;
}

  • 29
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是 Graham-Scan 算法C++ 实现,用于求解凸包问题: ```cpp #include <bits/stdc++.h> using namespace std; struct Point { int x, y; }; // 按照 x 坐标从小到大排序,若 x 坐标相等,则按照 y 坐标从小到大排序。 bool cmp(Point a, Point b) { if (a.x == b.x) return a.y < b.y; return a.x < b.x; } // 计算叉积。 int cross(Point a, Point b, Point c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); } // Graham-Scan 算法求解凸包。 vector<Point> grahamScan(vector<Point> &points) { int n = points.size(); if (n <= 1) return points; sort(points.begin(), points.end(), cmp); vector<Point> hull(2 * n); int k = 0; // 构建下凸壳。 for (int i = 0; i < n; ++i) { while (k >= 2 && cross(hull[k - 2], hull[k - 1], points[i]) <= 0) k--; hull[k++] = points[i]; } // 构建上凸壳。 for (int i = n - 2, t = k + 1; i >= 0; --i) { while (k >= t && cross(hull[k - 2], hull[k - 1], points[i]) <= 0) k--; hull[k++] = points[i]; } // 去除重复点。 hull.resize(k - 1); return hull; } int main() { // 测试数据。 vector<Point> points = {{0, 3}, {1, 1}, {2, 2}, {4, 4}, {0, 0}, {1, 2}, {3, 1}, {3, 3}}; vector<Point> hull = grahamScan(points); // 输出凸包的顶点。 for (int i = 0; i < hull.size(); ++i) { cout << "(" << hull[i].x << ", " << hull[i].y << ")" << endl; } return 0; } ``` 注意点: 1. 为了方便起见,我直接使用了 C++11 的新特性,使用 vector 存储点集,如果你使用的是较老的编译器,可以使用数组代替 vector。 2. 实现中为了方便起见,我使用了三个点 $A(a_x,a_y)$、$B(b_x,b_y)$、$C(c_x,c_y)$ 的叉积 $cross(A,B,C)$ 表示向量 $\vec{AB}$ 和 $\vec{AC}$ 的叉积。当叉积 $cross(A,B,C)>0$ 时,表示 $\vec{AB}$ 在 $\vec{AC}$ 的逆时针方向;当叉积 $cross(A,B,C)<0$ 时,表示 $\vec{AB}$ 在 $\vec{AC}$ 的顺时针方向;当叉积 $cross(A,B,C)=0$ 时,表示 $\vec{AB}$ 和 $\vec{AC}$ 共线。 3. 为了避免精度误差,最好使用整数类型存储坐标,如 int 类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值