前段时间看到生成凸包的Graham算法,查了一些资料,没看到c#版的,于是想动手写一个,该程序草草完成,其中有值得优化的地方,诸位可自行改正。

             Graham算法的原理:
    (1)给定一个点集P={P0,P1,.....Pn),找出点集中Y值最小的点,如果Y值最小的点有多个,可选择其中X值最小的
    (2) 假设上一步找出的点位P0,现在对剩下的点进行极角排序,按逆时针(本程序是这样,其他的方式是一样的)极角从小到大排序,假定排序结果集为 {p1,p2,p3,p4,....pn}。注意,这里不一定有N个点,因为可能存在几个点的极角相同,这时取据P0距最远的点,也或者你设定了一个极角 阈值,当两个点的极角差小于该阈值时,取据P0距离远的点,这时生成的凸包有一点点误差,误差的大小取决于你设置的阈值。本程序没采用阈值,所以生成的凸 包理论上不存在误差。
      在进行极角排序时,不需要真的算法每个点的极角(注意,这里的极角是该点与P0相对于X轴的夹角),只需要使用向量叉积来判断即可,这个过程我使用了链表来存储排序结果,因为这个过程会进行频繁的插入。
   (3)将p0,p1,p3入栈,p0,p1这两个点肯定在凸包上(原因很简单,p0不用解释了吧,p1因为它是极角最小的且为第一个点),p2则不一定在 凸包上,然后进行循环(for(int i=2;i<n;i++),判定栈顶的下一个点,栈顶点,及p[i]点这三点组成的折线段是否向左转,如果是的话,则p[i]入栈;否则,当前位于 栈顶的点不在凸包上,弹栈(该过程进行回朔,确保之前的所有点转向正确,这个步骤很重要,不然不能生成凸多边形),最后返回栈即可。
  下面是整个代码:
     算法部分:
   

凸包的c#实现算法 _c#
  1.  
    1. class ConvexAogrithm {  
    2.    private List<PointF> nodes;  
    3.    private Stack<PointF> sortedNodes;  
    4.    public PointF[] sor_nodes;  
    5.    public ConvexAogrithm(List<PointF> points)  
    6.       {  
    7.          nodes = points;  
    8.        }  
    9.      private double DistanceOfNodes(PointF p0, PointF p1)  
    10.        {  
    11.           if (p0.IsEmpty || p1.IsEmpty)  
    12.               return 0.0;  
    13.           return Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));  
    14.         }  
    15.     public void GetNodesByAngle( out PointF p0)  
    16.                     {  
    17.                             LinkedList<PointF> list_node = new LinkedList<PointF>();  
    18.                             p0 = GetMinYPoint();  
    19.                             LinkedListNode<PointF> node = new LinkedListNode<PointF>(nodes[0]);  
    20.                             list_node.AddFirst(node);  
    21.                             for (int i = 1; i < nodes.Count; i++)  
    22.                             {  
    23.                                     int direct = IsClockDirection(p0, node.Value, nodes[i]);  
    24.                                     if (direct == 1)  
    25.                                     {  
    26.                                              list_node.AddLast(nodes[i]);  
    27.                                              node = list_node.Last;  
    28.                                              //node.Value = nodes[i];  
    29.                                               
    30.                                     }  
    31.                                     else if (direct == -10)  
    32.                                     {  
    33.                                             list_node.Last.Value = nodes[i];  
    34.                                             //node = list_node.Last  
    35.                                             //node.Value = nodes[i];  
    36.                                     }  
    37.                                     else if (direct == 10)  
    38.                                             continue;  
    39.                                     else if (direct == -1)  
    40.                                     {  
    41.                                             LinkedListNode<PointF> temp = node.Previous;  
    42.                                             while (temp != null && IsClockDirection(p0, temp.Value, nodes[i]) == -1)  
    43.                                             {  
    44.                                                     temp = temp.Previous;  
    45.                                             }  
    46.                                             if (temp == null)  
    47.                                             {  
    48.                                                     list_node.AddFirst(nodes[i]);  
    49.                                                     continue;  
    50.                                             }  
    51.                                             if (IsClockDirection(p0, temp.Value, nodes[i]) == -10)  
    52.                                                     temp.Value = nodes[i];  
    53.                                             else if (IsClockDirection(p0, temp.Value, nodes[i]) == 10)  
    54.                                                     continue;  
    55.                                             else  
    56.                                                     list_node.AddAfter(temp, nodes[i]);  
    57.                                     }  
    58.                             }  
    59.                             sor_nodes = list_node.ToArray();  
    60.                             sortedNodes = new Stack<PointF>();  
    61.                             sortedNodes.Push(p0);  
    62.                             sortedNodes.Push(sor_nodes[0]);  
    63.                             sortedNodes.Push(sor_nodes[1]);  
    64.                             for (int i = 2; i<sor_nodes.Length; i++)  
    65.                             {  
    66.       
    67.                                     PointF p2 = sor_nodes[i];  
    68.                                     PointF p1 = sortedNodes.Pop();  
    69.                                     PointF p0_sec = sortedNodes.Pop();  
    70.                                     sortedNodes.Push(p0_sec);  
    71.                                     sortedNodes.Push(p1);  
    72.       
    73.                                     if (IsClockDirection1(p0_sec, p1, p2) == 1)  
    74.                                     {  
    75.                                             sortedNodes.Push(p2);  
    76.                                             continue;  
    77.                                     }  
    78.                                     while (IsClockDirection1(p0_sec, p1, p2) != 1)  
    79.                                     {  
    80.                                             sortedNodes.Pop();  
    81.                                             p1 = sortedNodes.Pop();  
    82.                                             p0_sec = sortedNodes.Pop();  
    83.                                             sortedNodes.Push(p0_sec);  
    84.                                             sortedNodes.Push(p1);  
    85.                                     }  
    86.                                     sortedNodes.Push(p2);  
    87.                             }  
    88.       
    89.                           
    90.                     }  
    91.                     private int IsClockDirection1(PointF p0, PointF p1, PointF p2)  
    92.                     {  
    93.                             PointF p0_p1 = new PointF(p1.X - p0.X, p1.Y - p0.Y);  
    94.                             PointF p0_p2 = new PointF(p2.X - p0.X, p2.Y - p0.Y);  
    95.                             return (p0_p1.X * p0_p2.Y - p0_p2.X * p0_p1.Y) > 0 ? 1 : -1;  
    96.                     }  
    97.                     private PointF GetMinYPoint()  
    98.                     {  
    99.                             PointF succNode;  
    100.                             float miny=nodes.Min(r=>r.Y);  
    101.                             IEnumerable<PointF> pminYs = nodes.Where(r => r.Y == miny);  
    102.                             PointF[] ps = pminYs.ToArray();  
    103.                             if (pminYs.Count() > 1)  
    104.                             {  
    105.                                     succNode = pminYs.Single(r => r.X == pminYs.Min(t => t.X));  
    106.                                     nodes.Remove(succNode);  
    107.                                     return succNode;  
    108.                             }  
    109.                             else  
    110.                             {  
    111.                                     nodes.Remove(ps[0]);  
    112.                                     return ps[0];  
    113.                             }  
    114.       
    115.                     }  
    116.                     private int IsClockDirection(PointF p0, PointF p1, PointF p2)  
    117.                     {  
    118.                             PointF p0_p1 = new PointF(p1.X-p0.X,p1.Y-p0.Y) ;  
    119.                             PointF p0_p2 = new PointF(p2.X - p0.X, p2.Y - p0.Y);  
    120.                             if ((p0_p1.X * p0_p2.Y - p0_p2.X * p0_p1.Y) != 0)  
    121.                                     return (p0_p1.X * p0_p2.Y - p0_p2.X * p0_p1.Y) > 0 ? 1 : -1;  
    122.                             else  
    123.                                     return DistanceOfNodes(p0, p1) > DistanceOfNodes(p0, p2) ? 10 : -10;  
    124.                                       
    125.                     }  
    126.                     public Stack<PointF> SortedNodes  
    127.                     {  
    128.                             get { return sortedNodes; }  
    129.                     }  
    130.       
    131.             }  
  2.  
 
界面部分,供测试使用:
凸包的c#实现算法 _c#
  1. public partial class Form1 : Form 
  2.         private List<PointF> nodes; 
  3.         private Graphics g; 
  4.         private Pen pen; 
  5.         public Form1() 
  6.         { 
  7.                 InitializeComponent(); 
  8.                 g=this.panel1.CreateGraphics(); 
  9.                 g.TranslateTransform(0f,this.panel1.Height); 
  10.                 g.ScaleTransform(1f,-1f); 
  11.                 pen = new Pen(Color.Blue); 
  12.         } 
  13.  
  14.         private void button2_Click(object sender, EventArgs e) 
  15.         { 
  16.                 g.Clear(panel1.BackColor); 
  17.                 nodes = new List<PointF>(); 
  18.                 nodes.Clear(); 
  19.                 Random rand = new Random(); 
  20.                 Point p = new Point(); ; 
  21.                 for (int i = 0; i < 1000; i++) 
  22.                 { 
  23.                         p.X = rand.Next(10, panel1.Width - 9); 
  24.                         p.Y = rand.Next(10, panel1.Height - 9); 
  25.                         nodes.Add(p); 
  26.                         DrawCircle(p); 
  27.                 } 
  28.              
  29.         } 
  30.         private void DrawCircle(Point p) 
  31.         { 
  32.                 g.DrawEllipse(pen, p.X - 4, p.Y - 4, 8, 8); 
  33.                 g.FillEllipse(Brushes.Blue, p.X - 4, p.Y - 4, 8, 8); 
  34.         } 
  35.  
  36.         private void button1_Click(object sender, EventArgs e) 
  37.         { 
  38.                 ConvexAogrithm ca = new ConvexAogrithm(nodes); 
  39.                 PointF p; 
  40.                 ca.GetNodesByAngle(out p); 
  41.                 //PointF[] ps = ca.sor_nodes; 
  42.                 //float[] psangle=new float[ps.Length]; 
  43.                 //for (int i = 0; i < psangle.Length; i++) 
  44.                      // psangle[i] = CalcAngle(p, ps[i]); 
  45.                 g.DrawEllipse(pen, p.X - 8, p.Y - 8, 16,16); 
  46.                 g.FillEllipse(Brushes.Blue, p.X - 8, p.Y - 8, 16, 16); 
  47.                 Stack<PointF> p_nodes = ca.SortedNodes; 
  48.                 pen = new Pen(Color.Black, 2.0f); 
  49.                 g.SmoothingMode = SmoothingMode.HighQuality; 
  50.                 pen.LineJoin = LineJoin.Round; 
  51.                 g.DrawPolygon(pen, p_nodes.ToArray()); 
  52.         } 
  53.         private float CalcAngle(PointF p1,PointF p2) 
  54.         { 
  55.                 float angle = (float)(Math.Atan(Math.Abs(p2.Y - p1.Y + 0.0) / Math.Abs(p2.X - p1.X + 0.0)) * 180 / Math.PI); 
  56.                 if ((p2.Y - p1.Y + 0.0) / (p2.X - p1.X + 0.0) < 0) 
  57.                         angle = 180 - angle; 
  58.                 return angle; 
  59.         } 
上面注释的为我当初测试排序极角的结果使用的,psangle是排序的结果,调试可看到角度从小到大。1000个点凸包如下(那个大圆点是P0,我为标记使用,无其他含义):
凸包的c#实现算法 _的_03
200个点的凸包如下:
凸包的c#实现算法 _的_04