這裏我列出了兩種方法:
1.基於質心的解法。(這種方法要求除非你擁有“分數”的數據結構,不然質心的求解會出現誤差)
2.基於向量的解法。
該算法的核心思想是:先求出凸包,凸包的對稱軸纔可能是整體的對稱軸,以此減小搜索範圍。在這過程中,可以使用各種方法(例如向量和爲0等等)來不斷減少不可能的情況,最終求出結果。
按理說,質心求解更快,但是苦於有精度誤差,所以還是方案2更好。
#include"iostream"
#include"math.h"
#include"vector"
#include"algorithm"
#include "iomanip"
#include"mex.h"
using namespace std;
#define PI 3.1415926
class Point//點類
{
public:
Point()
{
}
Point(double x, double y)//構造函數的重載
{
this->x = x;
this->y = y;
}
Point& operator=(const Point &A)//重載賦值運算符
{
this->x = A.x;
this->y = A.y;
return *this;
}
Point& operator+(const Point &A)//重載加號
{
static Point B;
B.x = this->x + A.x;
B.y = this->y + A.y;
return B;
}
Point& operator/(const double &A)//重載除號
{
static Point B;
B.x = this->x / A;
B.y = this->y / A;
return B;
}
double operator*(const Point &A)//重載向量的點乘
{
return x * A.y - y * A.x;
}
bool operator==(const Point&A)//重載相等比較符號
{
return (this->x == A.x && this->y == A.y);
}
bool operator!=(const Point&A)//重載不相等比較符號
{
return !(this->x == A.x && this->y == A.y);
}
void set(double x, double y)//賦值函數
{
this->x = x;
this->y = y;
}
Point To(Point& A)//返回向量
{
Point OA;
OA.x = A.x - this->x;
OA.y = A.y - this->y;
return OA;
}
double DistanceTo(Point B)//求距離函數
{
return sqrt((this->x - B.x)*(this->x - B.x) + (this->y - B.y)*(this->y - B.y));
}
double thea()//求到原點的極角
{
if (x == 0) return PI / 2;
else if (x > 0)
{
return atan(y / x);
}
else if (y > 0)
return PI + atan(y / x);
else
return atan(y / x) - PI;
}
double theaFrom(Point N)//求到點N的極角
{
double x = this->x - N.x;
double y = this->y - N.y;
if (x == 0) return PI / 2;
else if (x > 0)
{
return atan(y / x);
}
else if (y > 0)
return PI + atan(y / x);
else
return atan(y / x) - PI;
}
double x;
double y;
};
class Line
{
public:
Line() {}
Line(double a, double b, double c)//三值式
{
this->a = a;
this->b = b;
this->c = c;
if (a == -0) a = 0;
if (b == -0) b = 0;
if (c == -0) c = 0;
}
Line(Point A, double k)//點斜式
{
this->a = k;
this->b = -1;
this->c = -k * A.x + A.y;
if (a == -0) a = 0;
if (b == -0) b = 0;
if (c == -0) c = 0;
}
Line(Point A, Point B)//兩點式
{
if (A.x == B.x)
{
a = 1;
b = 0;
c = -A.x;
}
else
{
double k = (A.y - B.y) / (A.x - B.x);
a = k;
b = -1;
c = -k * A.x + A.y;
}
if (a == -0) a = 0;
if (b == -0) b = 0;
if (c == -0) c = 0;
OA = A.To(B);//得到特徵向量OA,用於輔助判斷是否垂直
O = A;
}
Line(Point A, Line L)//點垂式
{
if (L.b == 0)
{
a = 0;
b = 1;
c = -A.y;
}
else
if (L.a == 0)
{
a = 1;
b = 0;
c = -A.x;
}
else
{
double k = L.b / L.a;
a = k;
b = -1;
c = -k * A.x + A.y;
}
if (a == -0) a = 0;
if (b == -0) b = 0;
if (c == -0) c = 0;
}
Line& operator=(const Line &A)
{
this->a = A.a;
this->b = A.b;
this->c = A.c;
return *this;
}
bool operator==(Line &A)
{
if (this->a == A.a&&this->b == A.b &&this->c == A.c)
return true;
else
return false;
}
double DistanceFrom(Point A)//求點到直線的距離
{
double x, y;
if (a*A.x + b * A.y + c == 0)
return 0;
else
if (a == 0)
{
x = A.x;
y = -c / b;
}
else
{
y = (a*a*A.y - a * b*A.x - b * c) / (a*a + b * b);
x = (-c - b * y) / a;
}
return A.DistanceTo(Point(x, y));
}
int PointOnLine(Point A)//判斷點與直線的位置關係
{
double d = a * A.x + b * A.y + c;
if (d > 0) return 1;
else if (d < 0) return -1;
else return 0;
}
Point& SymmetryPoint(const Point &A)//對稱點
{
static Point A1;
if (a*A.x + b * A.y + c == 0)
A1 = A;
else
if (b == 0)
{
A1.x = 2 * (-c / a) - A.x;//a和b不可能同時爲0
A1.y = A.y;
}
else if (a == 0)
{
A1.x = A.x;
A1.y = 2 * (-c / b) - A.y;
}
else
{
A1.y = (-2 * a*b*A.x + (a*a - b * b)*A.y - 2 * b*c) / (a*a + b * b);
A1.x = a / b * (A1.y - A.y) + A.x;
}
return A1;
}
double a = 0, b = 0, c = 0;
Point OA = Point(0, 0);
Point O = Point(0, 0);
};
Point startPoint;//起點
Point centroid;//定義質心
vector<Point>::iterator it;//定義一個點循環指針,專門用於循環
vector<Point> Up;//點集的上半部分(由對稱軸確定的)
vector<Point> Down;//點集的下半部分(由對稱軸確定的)
vector<Line> Diad;//對稱軸的容器
bool on_segment(Point p, Point p1, Point p2)
{
double min_x = p1.x < p2.x ? p1.x : p2.x;
double max_x = p1.x > p2.x ? p1.x : p2.x;
double min_y = p1.y < p2.y ? p1.y : p2.y;
double max_y = p1.y > p2.y ? p1.y : p2.y;
if (p.x >= min_x && p.x <= max_x
&& p.y >= min_y && p.y <= max_y) return true;
else return false;
}
bool sortByPolorAngle(Point & p1, Point & p2)
{
Point op1 = startPoint.To(p1);
Point op2 = startPoint.To(p2);
double d = op1 * op2;//向量點乘,也就是叉積
if (d < 0) return true;//叉積小於0,說明是順時針旋轉了,我們需要
if (d > 0) return false;//叉積大於0,說明是逆時針旋轉了,我們不需要
if (d == 0 && on_segment(startPoint, p1, p2))return true;//共線的兩種情況,我們需要
if (d == 0 && on_segment(p2, startPoint, p1)) return true;
return false;
}
vector<Point>& GetConvexHull(vector<Point> & point)//求凸殼
{
Point p0 = point[0];//起點
int k = 0;
for (int i = 1; i < point.size(); i++)
{
if (point[i].y < p0.y ||
point[i].y == p0.y && point[i].x < p0.x)
{
p0 = point[i];
k = i;
}
}
point.erase(point.begin() + k);
point.insert(point.begin(), p0);//把起點調到開頭
static vector<Point> convex_hull;
do {
convex_hull.push_back(point[0]);
startPoint = point[0];//確定起點
point.erase(point.begin());//消去點集中的第一個點
sort(point.begin(), point.end(), sortByPolorAngle);//按照規則排序
point.push_back(convex_hull[convex_hull.size() - 1]);//把原來的開頭壓入到現在的結尾
if (point[0] == convex_hull[0]) break;//冒泡到了開頭了
} while (1);
for (vector<Point>::iterator it = convex_hull.begin(); it < convex_hull.end(); it++)
//將點集中的凸殼去除,便於之後的“對凸殼內部的點是否符合對稱軸”的驗證
{
vector<Point>::iterator it1 = point.begin();
while (it1 < point.end())
{
if ((*it) == (*it1))
{
point.erase(it1);
break;
}
else
it1++;
}
}
return convex_hull;
}
bool PointsCanSplit(vector<Point> v, Line L)//判斷直線是否可以劃分兩邊的點
{
Up.clear(); Down.clear();
int one = 0, two = 0;
for (vector<Point>::iterator it = v.begin(); it < v.end(); it++)
{
Point OB = L.O.To(*it);
if (L.OA*(OB) > 0)//利用叉積將點集其分爲上下兩部分
{
Up.push_back(*it);
one++;
}
if (L.OA*(OB) < 0)
{
Down.push_back(*it);
two++;
}
}
if (one == two) //如果上下兩部分點數相同,則返回真
return true;
else
return false;
}
bool find(vector<Point> v, Point A, vector<Point>::iterator &it1)
//查找函數,用於在點集中查找需要的點,並通過指針it1返回位置
{
vector<Point>::iterator it;
for (it = v.begin(); it < v.end(); it++)
{
if (*it == A)
{
it1 = it;
return true;
}
}
return false;
}
bool find(vector<Line> L, Line P, vector<Line>::iterator &it1)
{
//查找函數,用於在線集中查找需要的線,並通過指針it1返回位置
vector<Line>::iterator it;
for (it = L.begin(); it < L.end(); it++)
{
if (*it == P)
{
it1 = it;
return true;
}
}
return false;
}
bool IsSymmetric(vector<Point> A, Line L)//直接法判斷是否對稱(對稱點的求解有誤差)
{
vector<Point>::iterator it1;
for (it = Up.begin(); it < Up.end(); it++)//對凸殼的上半部分每個點
{
if (!find(Down, L.SymmetryPoint(*it), it1))
{
Point P = L.SymmetryPoint(*it);
return false;
}
}
return true;
}
bool IsSymmetric2(vector<Point> A, Line L)//用“垂線+距離”判斷法來判斷是否對稱。(垂線的求解有誤差)
{
vector<Point>::iterator it1;
for (it = Up.begin(); it < Up.end(); it++)//對凸殼的上半部分每個點
{
Line L1((*it), L);//點垂式求出直線
bool flag = 0;
for (it1 = Down.begin(); it1 < Down.end(); it1++)
{
if (L1.PointOnLine(*it1) == 0)
{
if (L.DistanceFrom(*it) == L.DistanceFrom(*it1))
flag = 1;
}
}
if (flag != 1) return false;
}
return true;
}
bool IsSymmetric3(vector<Point> A, Line L)//基於向量的垂直判斷+距離
{
vector<Point>::iterator it1;
for (it = Up.begin(); it < Up.end(); it++)//對凸殼的上半部分每個點
{
bool flag = 0;
for (it1 = Down.begin(); it1 < Down.end(); it1++)
{
Line L1(*it, *it1);
if (L1.OA.x*L.OA.x + L1.OA.y*L.OA.y == 0)//向量垂直的判斷
{
Point OB = L.O.To(((*it) + (*it1)) / 2);
if (L.OA*OB == 0)//向量平行的判斷
flag = 1;
}
}
if (flag != 1)
return false;
}
return true;
}
bool IsBalance(vector<Point> A, Line LX)//判斷點集內的點是否均勻
{
vector<Point>::iterator it;
double sum_x = 0, sum_y = 0, x, y;
Line LY(centroid, LX);
for (it = A.begin(); it < A.end(); it++)
{
double x_, y_;
x_ = LX.a*(*it).x + LX.b*(*it).y + LX.c;
y_ = LY.a*(*it).x + LY.b*(*it).y + LY.c;
if (x_ >= 0)
x = LY.DistanceFrom(*it);
else
x = -LY.DistanceFrom(*it);
if (y_ >= 0)
y = LX.DistanceFrom(*it);
else
y = -LX.DistanceFrom(*it);
sum_x = sum_x + x;
sum_y = sum_y + y;
}
if (sum_x == 0 && sum_y == 0)
return true;
else
return false;
}
void DealWithDiad(vector<Point> A, Line L)//對於對稱軸的處理
{
vector<Line>::iterator it1;
if (PointsCanSplit(A, L))
if (IsSymmetric3(A, L))
if (!find(Diad, L, it1))
Diad.push_back(L);
}
void Design1(vector<Point> A)//基於質心的求凸殼對稱軸方法,若質心的求解有誤差,則轉入枚舉法
{
double sum_x = 0, sum_y = 0;
int N = A.size();
for (int i = 0; i < N; i++)
{
sum_x = sum_x + A[i].x; sum_y = sum_y + A[i].y;
}
centroid.x = sum_x / N; centroid.y = sum_y / N;//求出質心
vector<Point> convex_hull = GetConvexHull(A);//求出凸殼
int Number = convex_hull.size() / 2;//只需要前一半的點數
double *AdjacentEdge = new double[Number + 1];//定義相鄰邊數組(只需要前半部分)
Point *EdgeMidpoint = new Point[Number + 1];//定義邊的中點數組(只需要前半部分)
vector<Line>::iterator it1;
if (convex_hull.size() == 2)//凸殼只有兩個點,對稱軸肯定是兩點連線和它的垂線了
{
Line L1(convex_hull[0], convex_hull[1]);
Line L2((convex_hull[0] + convex_hull[1]) / 2, L1);
Diad.push_back(L1);
Diad.push_back(L2);
return;
}
//有兩個點以上的情況:
for (int i = 0; i < Number + 1; i++)//求出前一半的中點,相鄰邊長
{
EdgeMidpoint[i] = (convex_hull[i] + convex_hull[i + 1]) / 2.0;
AdjacentEdge[i] = convex_hull[i].DistanceTo(convex_hull[i + 1]);
}
//判斷相鄰邊的中點與質心的連線是否爲凸殼對稱軸
for (int i = 0; i < Number + 1; i++)
{
Line L(EdgeMidpoint[i], centroid);
DealWithDiad(convex_hull, L);
}
//判斷相鄰邊的角平分線是否爲凸殼對稱軸
for (int i = 0; i < Number + 1; i++)
{
if (AdjacentEdge[i] == AdjacentEdge[i + 1])//當前邊與下一條邊相等
{
Line L(convex_hull[i + 1], centroid);
DealWithDiad(convex_hull, L);
}
}
}
void Design2(vector<Point> A)//用枚舉法求凸殼的對稱軸
{
vector<Point> convex_hull = GetConvexHull(A);//求出凸殼
int N = convex_hull.size();
if (N == 2)//凸殼只有兩個點,肯定是兩點連線和它的垂線了
{
Line L1(convex_hull[0], convex_hull[1]);
Line L2((convex_hull[0] + convex_hull[1]) / 2, L1);
Diad.push_back(L1);
Diad.push_back(L2);
return;
}
Point *EdgeMidpoint = new Point[N];//定義邊的中點數組
for (int i = 0; i < N; i++)
{
if (i == convex_hull.size() - 1)
EdgeMidpoint[i] = (convex_hull[i] + convex_hull[0]) / 2.0;
else
EdgeMidpoint[i] = (convex_hull[i] + convex_hull[i + 1]) / 2.0;
}
vector<Point> convex_hull_new;//新的包含中點的凸殼
for (int i = 0; i < N; i++)
{
convex_hull_new.push_back(convex_hull[i]);
}
for (int i = 0; i < N; i++)
{
convex_hull_new.push_back(EdgeMidpoint[i]);
}
N = convex_hull_new.size();
for (int i = 0; i < N; i++)
{
for (int j = i; j < N; j++)
{
if (i == j)
continue;
else
{
Line L(convex_hull_new[i], convex_hull_new[j]);//用兩點式求得直線
DealWithDiad(convex_hull, L);
}
}
}
}
/*
int main()
{
vector<Point> A;//定義點集
Point a;//定義一個點
cout << "請輸入點的個數:" << endl;
int N;
cin >> N;
cout << "請輸入點的橫縱座標:" << endl;
double x, y;
for (int i = 0; i < N; i++)
{
cin >> x;
cin >> y;
a.set(x, y);
A.push_back(a);
}
// Design1(A);//方案一,優化之後的方案
Design2(A);//方案二
if (A.size() != 0)//內部點集不爲空則進行驗證
{
for (vector<Line>::iterator it = Diad.begin(); it < Diad.end(); it++)//驗證內部點
{
if (IsBalance(A, (*it)))
{
if (IsSymmetric3(A, (*it)))
continue;
else
Diad.erase(it);
}
}
}
cout << "對稱軸的條數爲:" << Diad.size() << endl;
cout << "對稱軸爲:" << endl;
if (Diad.size() == 0)
cout << "無" << endl;
else
{
for (vector<Line>::iterator it = Diad.begin(); it < Diad.end(); it++)
{
cout << setw(20) << (*it).a << "*x + " << setw(20) << (*it).b << "*y + " << setw(20) << (*it).c << "=0" << endl;
}
}
system("pause");
return 0;
}*/
void Main(int N, double *x, double *y)
{
vector<Point> A;//定義點集
Point a;//定義一個點
for (int i = 0; i < N; i++)
{
a.set(x[i], y[i]);
A.push_back(a);
}
// Design1(A);//方案一,優化之後的方案
Design2(A);//方案二
if (A.size() != 0)//內部點集不爲空則進行驗證
{
for (vector<Line>::iterator it = Diad.begin(); it < Diad.end(); it++)//驗證內部點
{
if (IsBalance(A, (*it)))
{
if (IsSymmetric3(A, (*it)))
continue;
else
Diad.erase(it);
}
}
}
cout << "對稱軸的條數爲:" << Diad.size() << endl;
cout << "對稱軸爲:" << endl;
if (Diad.empty())
cout << "無" << endl;
else
{
for (auto it = Diad.begin(); it < Diad.end(); ++it)
{
cout << setw(20) << (*it).a << "*x + " << setw(20) << (*it).b << "*y + " << setw(20) << (*it).c << "=0" << endl;
}
}
}
兩種方法均列在這裏面。去掉註釋打開main即可使用。這其中Main函數就是執行函數,目前選中的執行方案是方案一,方案二大家可以自己改良。
這裏給出了Matlab的兼容性代碼,將這個和上面的代碼放在一起,在matlab中編譯,即可運行。
void mexFunction(int nlhs, mxArray*plhs[], int nrhs, const mxArray*prhs[])
{
int N;
double *inMatrix1;
double *inMatrix2;
if (nrhs != 4)
{
mexErrMsgIdAndTxt("MyToolbox:arrayProduct:nrhs", "There inputs required.");
}
if (nlhs != 0)
{
mexErrMsgIdAndTxt("MyToolbox:arrayProduct:nlhs", "Zero output requird.");
}
if (!mxIsDouble(prhs[0]) || mxIsComplex(prhs[10]) || mxGetNumberOfElements(prhs[0]) != 1)
{
mexErrMsgIdAndTxt("MyToolbox:arrayProduct:notScalar", "Input multiplier must be a scalar.");
}
if (mxGetM(prhs[1]) != 1)
{
mexErrMsgIdAndTxt("MyToolbox:arrayProduct:notRowVector", "Input must be a row vector.");
}
if (mxGetM(prhs[2]) != 1)
{
mexErrMsgIdAndTxt("MyToolbox:arrayProduct:notRowVector", "Input must be a row vector.");
}
N = mxGetScalar(prhs[0]);
inMatrix1 = mxGetPr(prhs[1]);
inMatrix2 = mxGetPr(prhs[2]);
Main(N, inMatrix1, inMatrix2);
}
該代碼均爲筆者手寫,如果有不懂可以提問,也能直接拿去使用。