سنتحدث اليوم عن مشكلة معروفة في مجال ال Computational Geometry وهي تحديد أصغر مضلع يحوي مجموعة من النقاط!
وقبل أن ندخل في حل المشكلة ، دعونا نتعرف على بعض المصطلحات .
Points وهي نقاط في المستوى Plane ، مثلا النقاط p(2,2) ,q(3,2),…etc في المستوى R x R ويمكن أن تكون في المستوى الثالث .
Polygon مضلع ، وقد يكون مثلث،مربع،دائرة،….الخ.
Convex وهو مضلع يحقق الخاصية التالية :
لكل نقطتين فيه p,q فان الخط الواصل بينها يقع داخل هذا الشكل .
Concave هو المضلع الذي لا يحقق الخاصية أعلاه.
Convex Hull هو أصغر مضلع Convex Polygon يحوي مجموعة من النقاط ، وهذا هو ما نريد ان نقوم بانشائه .
المضلع -باللون البرتقالي- هو ال convex hull .
خوارزمية QuickHull لايجاد الى convex-Hull:
يوجد العديد من الخوارزميات لايجاد هذا المضلع ، وسنذكر هنا خوارزمية QuickHull وهي خوارزمية تعمل بمبدأ Divide and Conquer حيث يتم تقسيم المشكلة الى اجزاء صغيرة -وهكذا يتم التقسيم الى ان نصل الى أصغر جزء ممكن- ثم تحل المشكلة وتجمع الحلول مع بعضها .
وتطبيقها سيكون بالاستدعاء الذاتي ، وهو يعطي كود جميل ومقروء، لذلك يفضل الكثير هذه الخوارزمية على الرغم من انه توجد بعض الخورازميات الاخرى ذات أداء أفضل لكن بكود أكثر تعقيدا !
وسأشرح الخوارزمية مع مثال نظري لكي تتضح أكثر.
نفرض ان لدينا مجموعة نقاط على المستوى :
الخطوة الاولى : ايجاد أصغر نقطة وأكبر نقطة على المحور الافقي – محور X
ومن ثم نوصل هاتين النقطتين معا .
هاتين النقطتين ستكون داخل المضلع النهائي Proved
هذا الخط سيقسم النقاط الى مجموعتين – فوق الخط وتحت الخط – لكن رياضيا نقول – يسار الخط ويمين الخط-
ونقول أن النقطة p تقع يسار الخط الواصل بين النقطتين u,v اذا كان حاصل الضرب الاتجاهي Cross Product للخط (المتجه) up و uv أكبر من الصفر ، اما اذا كان أقل من الصفر فهذا يعني أن النقطة p تقع يمين الخط (المتجه) uv.
ملاحظة : هناك عدة طرق اخرى لمعرفة مكان النقطة ، وسأستخدم هنا قانون الضرب الاتجاهي.
وكما هو معروف أن حاصل الضرب الاتجاهي لمتجهين هو متجه اخر عمودي على المتجهين ، هذا المتجه وليكن uw لديه اتجاه معين (موجب او سالب) ، ويحدد اتجاه هذا المتجه بناءا على موضع النقطة المراد معرفة مكانها ، فاذا كان المتجه up (الذي طرفه الاخير النقطة p ) يصنع زاوية بعكس عقارب الساعة مع المتجه uv فان حاصل الضرب الاتجاهي سيكون موجب ، اما اذا كان المتجه up يصنع زاوية في اتجاه عقارب الساعة clockwise فان حاص الضرب الاتجاهي مع uv سيكون سالب .
– سأحدث هذه الفقرة لاحقا لكي اضيف بعض الصور التوضيحية وكذلك رموز LaTeX للمعادلات -.
انظر في هذا ال Applet للمزيد :
http://physics.syr.edu/courses/java-suite/crosspro.html
نعود الان الى بقية خطوات الخوارزمية ، وقد وصلنا الى أن هناك مجموعتين من النقاط ، وسنشرح على المجموعة الاولى ، والثانية ستكون بنفس الفكرة.
الخطوة الثانية : ايجاد أبعد نقطة من الخط uv .
حيث أن أبعد نقطة من الخط ستكون داخل المضلع النهائي.
ولايجاد أبعد نقطة من خط ، توجد العديد من الطرق الرياضية ، ساستخدم المعادلة التالية :
– سأحدث هذه الفقرة لاحقا لكي أضيف رموز LaTeX للمعادلات -.
وكما تلاحظ من الشكل السابق ، أبعد نقطة قد شكلت لنا مثلث.
الخطوة الثالثة : استبعاد النقاط الموجودة داخل المثلث .
لانها بالطبع ليست نقاط عظمى Extreme Points
الخطوة الرابعة : كرر الخطوات 2 و 3 !
لان الخوارزمية تعمل بالطريقة التكرارية recursively فكل المطلوب الان هو أن نمسك الضلع (اليمين واليسار) في المثلث كل على حدة ، ثم نقوم بايجاد ابعد نقطة عن الخط وبعدها سيظهر لنا مثلث ، وسنتجاهل النقاط بداخله ، …. وهكذا .
متى سنتوقف ؟
بالتأكيد عندما لا نجد أي نقطة فوق الخط uv مثلا.
الخطوة الخامسة : كرر الخطوات على الجزء الاخر (الذي يقع يمين الخط) ، وهكذا تكون قد انتهت الخوارزمية
تنفيذ الخوارزمية
Implementation of QuickHull in C++/Qt
#ifndef QUICKHULL_H #define QUICKHULL_H #includeclass QLine; class QuickHull { public: QuickHull(const QPolygon& polygon); static QPoint minPoint(const QPolygon& polygon); static QPoint maxPoint(const QPolygon& polygon); const QPolygon& convexHull(); private: void findHull(QPoint p1,QPoint p2,QPolygon set); static bool isLeft(const QLine& segment,const QPoint& point); static int distance(const QPoint& p1,const QPoint& p2,const QPoint& p3); static QPoint maxPointDistance(const QPoint& A,const QPoint& B,const QPolygon& set); QPolygon m_points; QPolygon m_convexhull; }; #endif // QUICKHULL_H
الفئة QPolygon هي عبارة عن مصفوفة من النقاط .
دالة ايجاد أصغر نقطة :
/* Find the minimum point in the horizontal axis */ QPoint QuickHull::minPoint(const QPolygon& polygon) { QPoint min = polygon.at(0); foreach (QPoint p,polygon) { if ( min.x() < p.x() ) min = p; } return min; }
ايجاد أكبر نقطة :
/* Find the maximum point in the horizontal axis */ QPoint QuickHull::maxPoint(const QPolygon& polygon) { QPoint max = polygon.at(0); foreach (QPoint p,polygon) { if ( max.x() > p.x() ) max = p; } return max; }
معرفة مكان النقطة بالنسبة لخط ما ، يمين الخط ام يساره؟
/* Check if point P is left to the line segment AB or not. ** This is done by take the Cross product AB x AP. ** if the result is positive (angle are counter-clockwise) it return true, else return false. */ bool QuickHull::isLeft(const QLine& line,const QPoint& p) { int cp = (line.x2()-line.x1()) * (p.y()-line.y1()) - (line.y2()-line.y1()) * (p.x()-line.x1()); return (cp>0)?true:false; }
حساب أبعد نقطة من خط ما ، لاحظ ان القانون ليس دقيقا فهو يتجاهل الكثير من الاشياء التي لن تأثر على المسافة
/* Compute the (Pseudo)distance between the line segment (p1,p2) and the point p3. */ int QuickHull::distance(const QPoint& p1,const QPoint& p2,const QPoint& p3) { int ux = p2.x() - p1.x(); int uy = p2.y() - p1.y(); int s = ux*(p1.y()-p3.y()) - uy*(p1.x()-p3.x()); if (s<0) s = -s; return s; }
بداية الخوارزمية
/* Start of QuickHull Algorithm */ const QPolygon& QuickHull::convexHull() { if ( m_points.size() < 3 ) return m_points; // Find the min/max point in the convex polygon QPoint A = minPoint(m_points); QPoint B = maxPoint(m_points); QLine segment(A,B); // Add min/max point to the m_convexhull m_convexhull << A << B; // Remove min/max from the set of all points m_points m_points.remove(m_points.indexOf(A)); m_points.remove(m_points.indexOf(B)); // Find the left and right points lies with the line segment (A,B). // This line divide the hull into upper and lower hull. QPolygon upperHull; QPolygon lowerHull; foreach (QPoint p,m_points) { if (isLeft(segment,p)) upperHull << p; else lowerHull << p; } findHull(A,B,upperHull); findHull(B,A,lowerHull); return m_convexhull; }
الدالة التكرارية - تقسيم المشكلة وجمع الحلول هنا-
void QuickHull::findHull(QPoint A,QPoint B,QPolygon set) { // Place to put the new extreme point int index = m_convexhull.indexOf(B); if ( set.size() == 0 ) return; if ( set.size() == 1 ) { QPoint p = set.at(0); set.remove(set.indexOf(p)); m_convexhull.insert(index,p); return; } // Find point that maximize the area of the triangle. QPoint C = maxPointDistance(A,B,set); // Add this point to the convex hull(proved) set.remove(set.indexOf(C)); m_convexhull.insert(index,C); // Add points left of AC to leftAC set and add points left of CB to leftCB // This will automatically discard all points inside this triangle ACB. QPolygon leftAC; QPolygon leftCB; foreach (QPoint q,set) { if (isLeft(QLine(A,C),q)) leftAC << q; } foreach (QPoint r,set) { if (isLeft(QLine(C,B),r)) leftCB << r; } findHull(A,C,leftAC); findHull(C,B,leftCB); }
وفي الملف المرفق ستجد مثال بسيط Quick & Dirty، وكل ما عليك ان تحدد مجموعة من النقاط ثم تضغط على زر start وسيتم تحديد ال convex hull.
تطبيقات على الخوارزمية :
Computer Visualization, Ray Tracing, Video Games, Replacement of Bounding Boxes
Path Finding - Embedded AI of Mars mission Rovers
Geographical Information Systems (GIS) - Computing Accessibility Maps
Visual Pattern Matching - Detecting Car License Plates
Verification Methods - Bounding of Number Decision Diagrams
Geometry - Diameter Computation
http://www.tcs.fudan.edu.cn/rudolf/Courses/Algorithms/Alg_ss_07w/Webprojects/Chen_hull/applications.htm
روابط :
http://en.wikipedia.org/wiki/Convex_hull
http://westhoffswelt.de/blog/0040_quickhull_introduction_and_php_implementation.html
http://www.cse.yorku.ca/~aaw/Hang/quick_hull/Algorithm.html
http://www.ahristov.com/tutorial/geometry-games/convex-hull.html
مرفق:
convexhull.tar
أكثر من رائع فوجود محتوى عربي بهده القيمة شيئ يثلج الصدر زيادةً على طريقة شرح المتميزة شكرا لكم
شي جيمل ان نجد محتوى عربي يقوم بكتابة مقالات مميزة بهذا شكل والله .
جزاكم الله خيرا
مفيش شرح فديو بالعربى