www.pudn.com > testConvexHull.rar > testConvexHull.cpp
// testConvexHull.cpp : Defines the entry point for the console application. // #include "stdafx.h" // // This program uses the Graham scan algorithm to calculate the convex // hull of a batch of points // #include#include #include #include #include #include std::ostream &operator <<(std::ostream &s, const std::pair &point ) { s << "(" << point.first << "," << point.second << ")"; return s; } class GrahamScan { public : GrahamScan( size_t n, int xmin, int xmax, int ymin, int ymax ) : N( n ) , x_range( xmin, xmax ) , y_range( ymin, ymax ) { // // In this constructor I generate the N random points asked for // by the caller. Their values are randomly assigned in the x/y // ranges specified as arguments // srand( static_cast ( time(NULL) ) ); for ( size_t i = 0 ; i < N ; i++ ) { int x = ( rand() % ( x_range.second - x_range.first + 1 ) ) + x_range.first; int y = ( rand() % ( y_range.second - y_range.first + 1 ) ) + y_range.first; raw_points.push_back( std::make_pair( x, y ) ); } } // // The initial array of points is stored in vectgor raw_points. I first // sort it, which gives me the far left and far right points of the hull. // These are special values, and they are stored off separately in the left // and right members. // // I then go through the list of raw_points, and one by one determine whether // each point is above or below the line formed by the right and left points. // If it is above, the point is moved into the upper_partition_points sequence. If it // is below, the point is moved into the lower_partition_points sequence. So the output // of this routine is the left and right points, and the sorted points that are in the // upper and lower partitions. // void partition_points() { // // Step one in partitioning the points is to sort the raw data // std::sort( raw_points.begin(), raw_points.end() ); // // The the far left and far right points, remove them from the // sorted sequence and store them in special members // left = raw_points.front(); raw_points.erase( raw_points.begin() ); right = raw_points.back(); raw_points.pop_back(); // // Now put the remaining points in one of the two output sequences // for ( size_t i = 0 ; i < raw_points.size() ; i++ ) { int dir = direction( left, right, raw_points[ i ] ); if ( dir < 0 ) upper_partition_points.push_back( raw_points[ i ] ); else lower_partition_points.push_back( raw_points[ i ] ); } } // // Building the hull consists of two procedures: building the lower and // then the upper hull. The two procedures are nearly identical - the main // difference between the two is the test for convexity. When building the upper // hull, our rull is that the middle point must always be *above* the line formed // by its two closest neighbors. When building the lower hull, the rule is that point // must be *below* its two closest neighbors. We pass this information to the // building routine as the last parameter, which is either -1 or 1. // void build_hull( std::ofstream &f ) { build_half_hull( f, lower_partition_points, lower_hull, 1 ); build_half_hull( f, upper_partition_points, upper_hull, -1 ); } // // This is the method that builds either the upper or the lower half convex // hull. It takes as its input a copy of the input array, which will be the // sorted list of points in one of the two halfs. It produces as output a list // of the points in the corresponding convex hull. // // The factor should be 1 for the lower hull, and -1 for the upper hull. // void build_half_hull( std::ostream &f, std::vector< std::pair > input, std::vector< std::pair > &output, int factor ) { // // The hull will always start with the left point, and end with the right // point. According, we start by adding the left point as the first point // in the output sequence, and make sure the right point is the last point // in the input sequence. // input.push_back( right ); output.push_back( left ); // // The construction loop runs until the input is exhausted // while ( input.size() != 0 ) { // // Repeatedly add the leftmost point to the null, then test to see // if a convexity violation has occured. If it has, fix things up // by removing the next-to-last point in the output suqeence until // convexity is restored. // output.push_back( input.front() ); plot_hull( f, "adding a new point" ); input.erase( input.begin() ); while ( output.size() >= 3 ) { size_t end = output.size() - 1; if ( factor * direction( output[ end - 2 ], output[ end ], output[ end - 1 ] ) <= 0 ) { output.erase( output.begin() + end - 1 ); plot_hull( f, "backtracking" ); } else break; } } } // // In this program we frequently want to look at three consecutive // points, p0, p1, and p2, and determine whether p2 has taken a turn // to the left or a turn to the right. // // We can do this by by translating the points so that p1 is at the origin, // then taking the cross product of p0 and p2. The result will be positive, // negative, or 0, meaning respectively that p2 has turned right, left, or // is on a straight line. // static int direction( std::pair p0, std::pair p1, std::pair p2 ) { return ( (p0.first - p1.first ) * (p2.second - p1.second ) ) - ( (p2.first - p1.first ) * (p0.second - p1.second ) ); } void log_raw_points( std::ostream &f ) { f << "Creating raw points:\n"; for ( size_t i = 0 ; i < N ; i++ ) f << raw_points[ i ] << " "; f << "\n"; } void log_partitioned_points( std::ostream &f ) { f << "Partitioned set:\n" << "Left : " << left << "\n" << "Right : " << right << "\n" << "Lower partition: "; for ( size_t i = 0 ; i < lower_partition_points.size() ; i++ ) f << lower_partition_points[ i ]; f << "\n"; f << "Upper partition: "; for ( i = 0 ; i < upper_partition_points.size() ; i++ ) f << upper_partition_points[ i ]; f << "\n"; } void log_hull( std::ostream & f ) { f << "Lower hull: "; for ( size_t i = 0 ; i < lower_hull.size() ; i++ ) f << lower_hull[ i ]; f << "\n"; f << "Upper hull: "; for ( i = 0 ; i < upper_hull.size() ; i++ ) f << upper_hull[ i ]; f << "\n"; f << "Convex hull: "; for ( i = 0 ; i < lower_hull.size() ; i++ ) f << lower_hull[ i ] << " "; for ( std::vector< std::pair >::reverse_iterator ii = upper_hull.rbegin() + 1 ; ii != upper_hull.rend(); ii ++ ) f << *ii << " "; f << "\n"; } void plot_raw_points( std::ostream &f ) { f << "set xrange [" << x_range.first << ":" << x_range.second << "]\n"; f << "set yrange [" << y_range.first << ":" << y_range.second << "]\n"; f << "unset mouse\n"; f << "set title 'The set of raw points in the set' font 'Arial,12'\n"; f << "set style line 1 pointtype 7 linecolor rgb 'red'\n"; f << "set style line 2 pointtype 7 linecolor rgb 'green'\n"; f << "set style line 3 pointtype 7 linecolor rgb 'black'\n"; f << "plot '-' ls 1 with points notitle\n"; for ( size_t i = 0 ; i < N ; i++ ) f << raw_points[ i ].first << " " << raw_points[ i ].second << "\n"; f << "e\n"; f << "pause -1 'Hit OK to move to the next state'\n"; } void plot_partitioned_points( std::ostream &f ) { f << "set title 'The points partitioned into an upper and lower hull' font 'Arial,12'\n"; f << "plot '-' ls 1 with points notitle, " << "'-' ls 2 with points notitle, " << "'-' ls 3 with linespoints notitle\n"; for ( size_t i = 0 ; i < lower_partition_points.size() ; i++ ) f << lower_partition_points[ i ].first << " " << lower_partition_points[ i ].second << "\n"; f << "e\n"; for ( i = 0 ; i < upper_partition_points.size() ; i++ ) f << upper_partition_points[ i ].first << " " << upper_partition_points[ i ].second << "\n"; f << "e\n"; f << left.first << " " << left.second << "\n"; f << right.first << " " << right.second << "\n"; f << "e\n"; f << "pause -1 'Hit OK to move to the next state'\n"; } void plot_hull( std::ostream &f, std::string text ) { f << "set title 'The hull in state: " << text << "' font 'Arial,12'\n"; f << "plot '-' ls 1 with points notitle, "; if ( lower_hull.size() ) f << "'-' ls 3 with linespoints notitle, "; if ( upper_hull.size() ) f << "'-' ls 3 with linespoints notitle, "; f << "'-' ls 2 with points notitle\n"; for ( size_t i = 0 ; i < lower_partition_points.size() ; i++ ) f << lower_partition_points[ i ].first << " " << lower_partition_points[ i ].second << "\n"; f << right.first << " " << right.second << "\n"; f << "e\n"; if ( lower_hull.size() ) { for ( size_t i = 0 ; i < lower_hull.size() ; i++ ) f << lower_hull[ i ].first << " " << lower_hull[ i ].second << "\n"; f << "e\n"; } if ( upper_hull.size() ) { for ( std::vector< std::pair >::reverse_iterator ii = upper_hull.rbegin(); ii != upper_hull.rend(); ii++ ) f << ii->first << " " << ii->second << "\n"; f << "e\n"; } for ( i = 0 ; i < upper_partition_points.size() ; i++ ) f << upper_partition_points[ i ].first << " " << upper_partition_points[ i ].second << "\n"; f << "e\n"; f << "pause -1 'Hit OK to move to the next state'\n"; } private : // // These values determine the range of numbers generated to // provide the input data. The values are all passed in as part // of the constructor // const size_t N; const std::pair x_range; const std::pair y_range; // The raw data points generated by the constructor std::vector< std::pair > raw_points; // // These values are used to represent the partitioned set. A special // leftmost and rightmost value, and the sorted set of upper and lower // partitioned points that lie inside those two points. // std::pair left; std::pair right; std::vector< std::pair > upper_partition_points; std::vector< std::pair > lower_partition_points; // // After the convex hull is created, the lower hull and upper hull // are stored in these sorted sequences. There is a bit of duplication // between the two, because both sets include the leftmost and rightmost point. // std::vector< std::pair > lower_hull; std::vector< std::pair > upper_hull; }; int main(int argc, char* argv[]) { std::ofstream gnuplot_file( "gnuplot.cmd" ); const int N = 10; GrahamScan g( N, 0, 100, 0, 100 ); g.log_raw_points( std::cout ); g.plot_raw_points( gnuplot_file ); g.partition_points(); g.log_partitioned_points( std::cout ); g.plot_partitioned_points( gnuplot_file ); // // Okay, time to start building the hull // g.build_hull( gnuplot_file ); g.log_hull( std::cout ); g.plot_hull( gnuplot_file, "complete" ); return 0; }