判斷扇形和圓是否相交
面試的時候被問到如何判斷扇形和圓是否相交,當時也是比較懵的,刷leetcode不會碰到這種問題。我大概分了幾個情況進行討論,並且用到了求點到直線的距離公式。面試官後來提示說從幾何的角度考慮,最好不要去用方程求解的角度去想,,,大概想到了向量點乘和叉乘,但是當時沒寫出來。後來在網上看博客大部分也都是去解方程,稍微麻煩一些,現在把自己的思路分享一下,有錯誤歡迎指正。
一般大家首先會考慮到分成扇形邊和弧段部分分別判斷吧,我們先看一下如何判斷線段是否和圓形相交:
線段和圓形是否相交
我們用兩個端點表示線段,用一個點和半徑表示圓
typedef pair<float, float> Point;
Point l1(-1.1,0), l2(5, 0.9), c1(0, 0);
float R;
首先點和圓形的拓撲關係很好計算,根據端點和圓形的關係分成三種情況,即兩個端點都在圓形內,一個端點在圓形內,兩個端點都不在圓形內。第一種情況不相交,第二種情況相交,第三種情況需要進一步討論。
對這三種情況進行判別只需要一個點點距離公式:
float pointDis(Point p1, Point p2)
{
return sqrt((p1.first - p2.first) * (p1.first - p2.first) + (p1.second - p2.second) * (p1.second - p2.second));
}
bool inCircle(Point p, Point c1, float R)
{
float dis = pointDis(p, c1);
if (dis < R)
return true;
return false;
}
下面對第三種情況進行進一步的討論:
如果圓心到這個直線的距離小於半徑,且垂足落在線段上,那麼相交,否則不相交。爲了完成這個計算,我們引入向量:
typedef pair<float, float> vector2d;
有了向量能做的事情就多了,不需要用點到直線的距離公式,也不需要對直線用公式建模(需要判斷特殊情況),計算角度也很方便。下面看一下叉乘:
float crossProduct(vector2d v1, vector2d v2)
{
return (v1.first * v2.second - v1.second * v2.first);
}
二維空間下,叉乘的結果是,是和兩個邊的夾角,其實二維空間下叉乘結果是有正負號的(表示沿隱藏的z軸方向還是其反方向),這裏我們不需要用到這個特性,所以可以直接取絕對值。
下面看一下點乘:
float dotProduct(vector2d v1, vector2d v2)
{
return (v1.first * v2.first + v1.second * v2.second);
}
點乘的結果是,顯然的正負可以表示一個角是銳角還是鈍角。
好的我們有了向量、點乘和叉乘,接下來思考如何避免對直線建模後再去計算距離,以及如何去判斷角度:
向量表示:
vector2d vecl2_l1(l1.first - l2.first, l1.second - l2.second);
vector2d vecl1_l2(-vecl2_l1.first, -vecl2_l1.second);
vector2d vecc1_l1(l1.first - c1.first, l1.second - c1.second);
vector2d vecc1_l2(l2.first - c1.first, l2.second - c1.second);
叉乘計算距離:
float dis = abs(crossProduct(vecc1_l1, vecl2_l1) / pointDis(l1, l2));
沒錯,就是這麼簡單。接下來用點乘判斷夾角:
if (dis > R) return false;
bool flag = dotProduct(vecl1_l2, vecc1_l2) && dotProduct(vecl2_l1, vecc1_l1);
如果兩個夾角都是銳角,flag爲true,反之flag爲false,全部代碼如下:
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<float, float> Point;
typedef pair<float, float> vector2d;
// 點距
float pointDis(Point p1, Point p2)
{
return sqrt((p1.first - p2.first) * (p1.first - p2.first) + (p1.second - p2.second) * (p1.second - p2.second));
}
// ×乘
float crossProduct(vector2d v1, vector2d v2)
{
return (v1.first * v2.second - v1.second * v2.first);
}
// 點乘
float dotProduct(vector2d v1, vector2d v2)
{
return (v1.first * v2.first + v1.second * v2.second);
}
//點是否在圓內
bool inCircle(Point p, Point c1, float R)
{
float dis = pointDis(p, c1);
if (dis < R)
return true;
return false;
}
//線段與圓是否相交
bool judgeCircleAndLine(Point l1, Point l2, Point c1, float R)
{
// 兩個端點都在圓內'
if (inCircle(l1, c1, R) && inCircle(l2, c1, R)) return false;
// 一個端點在圓內
if (inCircle(l1, c1, R) || inCircle(l2, c1, R)) return true;
// 兩個點都不在圓內
vector2d vecl2_l1(l1.first - l2.first, l1.second - l2.second);
vector2d vecl1_l2(-vecl2_l1.first, -vecl2_l1.second);
vector2d vecc1_l1(l1.first - c1.first, l1.second - c1.second);
vector2d vecc1_l2(l2.first - c1.first, l2.second - c1.second);
float dis = abs(crossProduct(vecc1_l1, vecl2_l1) / pointDis(l1, l2));
if (dis > R) return false;
bool flag = dotProduct(vecl1_l2, vecc1_l2) && dotProduct(vecl2_l1, vecc1_l1);
return flag;
}
int main()
{
Point l1(-1.1,0), l2(0,1.1), c1(0, 0);
std::cout <<judgeCircleAndLine(l1,l2,c1,1);
}
暫時寫到這裏,有問題歡迎指正,後邊會繼續些扇形的問題。。