最小外接矩形問題是在給出一個多邊形(或一群點),求出面積最小且外接多邊形的矩形的問題。這個問題看起來並不難,但是具體實現並不簡單。除了調用現有的公開庫之外,這里給出一種簡單且易理解的方法。
算法的主要思想是:
(1)先實現多邊形的簡單外接矩形的算法。簡單外接矩形是指邊平行於x軸或y軸的外接矩形。簡單外接矩形很有可能不是最小外接矩形,卻是非常容易求得的外接矩形,這為后面做鋪墊。
(2)實現平面上某一點繞固定點旋轉某一角度的算法。數學基礎是,設平面上點(x1,y1)繞另一點(x0,y0)逆時針旋轉A角度后的點為(x2,y2),則有
x2 =(x1-x0)*cosA-(y1-y0)*sinA+x0
y2 =(x1-x0)*sinA+(y1-y0)*cosA+y0
順時針時,A改寫成-A即可。
(3)旋轉原始多邊形(循環,0-90°,間距設為1°),求旋轉每個度數后的多邊形的簡單外接矩形,記錄簡單外接矩形的面積、頂點坐標以及此時旋轉的度數。
(4)比較在旋轉過程中多邊形求得的所有簡單外接矩形,得到面積最小的簡單外接矩形,獲取該簡單外接矩形的頂點坐標和旋轉的角度。
(5)旋轉外接矩形。將上一步獲得面積最小的簡單外接矩形反方向(與第3步方向相反)旋轉相同的角度,即得最小外接矩形。
實現過程
(1)尋找多邊形的中心
多邊形的中心就是多邊形的重心,各個坐標點之和求平均即可
CPoint* CGeoPolygon::FindCenter(vector<CPoint*> ptsArray) { double tempX,tempY; double sumX = 0; double sumY = 0; int size = ptsArray.size(); for(int i = 0;i<size;i++) { sumX = sumX + ptsArray[i]->x; sumY = sumY + ptsArray[i]->y; } tempX = sumX/size; tempY = sumY/size; CPoint* pt = new CPoint(tempX,tempY); return pt; }
(2)旋轉多邊形,針對每個點實現繞中心點旋轉
旋轉的算法見下,注意角度要轉換成弧度。
// 某一點pt繞center旋轉theta角度,zf,0706 CPoint* CGeoPolygon::rotate(CPoint* pt, CPoint* center, double theta) { double x1 = pt->x; double y1 = pt->y; double x0 = center->x; double y0 = center->y; double Q = theta / 180 * 3.1415926; //角度 double x2,y2; x2 = (x1-x0)*cos(Q)-(y1-y0)*sin(Q)+x0; //旋轉公式 y2 = (x1-x0)*sin(Q)+(y1-y0)*cos(Q)+y0; CPoint* rotatePoint = new CPoint(x2,y2); //CPoint* rotatePoint = new CPoint((x1+10,y1+10)); return rotatePoint; }
(3)多邊形旋轉后求簡單外接矩形,簡單外接矩形算法見下
void CGeoPolygon::FindRectangle(vector<CMyPoint*> pts) { //AfxMessageBox("一般的外接矩形!"); int size = pts.size(); if(size == 0) AfxMessageBox("該環為空"); else { double Xmax = 0; double Ymax = 0; double Xmin = 60000000; //最小值不能初始為0, double Ymin = 1000000000; //最小值不能初始為0, for(int i = 0;i<size;i++) { double tempx = pts[i]->Getx(); double tempy = pts[i]->Gety(); if(tempx>=Xmax) Xmax = tempx; //最大x, if(tempy>=Ymax) Ymax = tempy; //最大y, if(tempx<=Xmin) Xmin = tempx; //最小x if(tempy<=Ymin) Ymin = tempy; //最小y } CPoint *pt1 = new CPoint(Xmax,Ymax); //左上 CPoint *pt2 = new CPoint(Xmax,Ymin); CPoint *pt3 = new CPoint(Xmin,Ymin); CPoint *pt4 = new CPoint(Xmin,Ymax); rectangleArray.push_back(pt1); rectangleArray.push_back(pt2); rectangleArray.push_back(pt3); rectangleArray.push_back(pt4); } }
這里的rectangleArray是我自己工程的數組,可以換成自己的。
上述三步為第一大步。
(4)存儲每個旋轉角度下多邊形的外接矩形,記錄外接矩形的頂點坐標、面積和此時多邊形的旋轉角度
vector<CPoint*> temp = FindRectangle(tempArray); //-----------2---------求旋轉后的外接矩形 if(temp.size() == 0) AfxMessageBox("簡單外接矩形獲取失敗!"); else { MBR_ZF *tempRect = new MBR_ZF(); //某個旋轉角度時的外接矩形指針 for(int count = 0;count<temp.size();count++) //將外接矩形頂點轉移到circles的變量中 tempRect->vertices.push_back(temp[count]); double deltaX,deltaY,tempS; //求每個外接矩形的面積 deltaX = tempRect->vertices[0]->x - tempRect->vertices[2]->x; deltaY = tempRect->vertices[0]->y - tempRect->vertices[2]->y; tempS = deltaY * deltaX; tempRect->area = tempS; tempRect->ID = angle; circles[i]->mbr.push_back(tempRect); //mbr是用於存儲旋轉過程中每個外接矩形的數組,單組成元是每個對象的指針 }
(5)比較每個外接矩形,確定每個環面積最小的外接矩形。
//------3-----------比較每個外接矩形,確定每個環面積最小的外接矩形 int finalID; double compare = 600000000; for(int num = 0;num<circles[i]->mbr.size();num++) { if(compare >= circles[i]->mbr[num]->area) { finalID = circles[i]->mbr[num]->ID; compare = circles[i]->mbr[num]->area; } } for(int num = 0;num<circles[i]->mbr.size();num++) { if(circles[i]->mbr[num]->ID == finalID) { circles[i]->finalMBR.area = circles[i]->mbr[num]->area; circles[i]->finalMBR.ID = circles[i]->mbr[num]->ID; for(int pointNum = 0; pointNum<circles[i]->mbr[num]->vertices.size();pointNum++) circles[i]->finalMBR.vertices.push_back(circles[i]->mbr[num]->vertices[pointNum]); } }
(6)將外接矩形旋轉回來。外接矩形朝相反的方向旋轉相同度數。
//----------4-------將外接矩形朝相反的方向旋轉相同度數 int finalAngle = circles[i]->finalMBR.ID; for(int final = 0;final<circles[i]->finalMBR.vertices.size();final++) rectangleRotate.push_back(rotate(circles[i]->finalMBR.vertices[final],center,-finalAngle)); //!!!此處角度相反
ID就是旋轉的角度。
最終效果:
簡單外接矩形
最小外接矩形
總結:理解算法的思路很重要,我的代碼只是一個例子。這個方法不需要旋轉外接矩形,只需要旋轉多邊形求簡單外接矩形,思路上更容易理解。
PS:很感謝我的室友楊某給我的幫助,此方法受他啟發。