C++:給定一個二維點集,找到所有的整體對稱軸

這裏我列出了兩種方法:

 

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);
}

該代碼均爲筆者手寫,如果有不懂可以提問,也能直接拿去使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章