Advertisement
dmkozyrev

convex_hull.hpp

Jan 17th, 2016
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 16.38 KB | None | 0 0
  1. //convex_hull.hpp
  2.  
  3. #pragma once
  4.  
  5. #include <iostream>
  6. #include <fstream>
  7. #include <vector>
  8. #include <algorithm>
  9. #include <math.h>
  10.  
  11. // Определения типов, с которыми работают функции:
  12. typedef int TypeOfPoint; // Тип точки
  13. typedef std::pair<TypeOfPoint, TypeOfPoint> Point; // Точка
  14. typedef std::vector<Point> Points; // Вектор из точек
  15.  
  16. // Создание выпуклой оболочки из файла с точками:
  17. void create_convex_hull_from_file(Points & hull, const std::string & filename);
  18.  
  19. // Создание выпуклой оболочки из вектора точек:
  20. void create_convex_hull_from_points(Points & hull, const Points & points);
  21.  
  22. // Добавление точки в выпуклую оболочку:
  23. void add_to_convex_hull(Points & hull, const Point & p); // Добавление точки в произвольную выпуклую оболочку
  24. void add_to_zero_convex_hull(Points & hull, const Point & p); // В пустую выпуклую оболочку
  25. void add_to_one_convex_hull(Points & hull, const Point & p); // В выпуклую оболочку, состоящую из одной точки
  26. void add_to_two_convex_hull(Points & hull, const Point & p); // В выпуклую оболочку, состоящую из двух точек
  27. void add_to_other_convex_hull(Points & hull, const Point & p); // В выпуклую оболочку, состоящую из более, чем двух точек
  28.  
  29. // Модифицирование выпуклой оболочки:
  30. void shift_points_left(Points & hull, int k); // Циклический сдвиг всех вершин влево на k элементов
  31. void shift_points_right(Points & hull, int k); // Циклический сдвиг всех вершин вправо на k элементов
  32. void erase_from_points(Points & hull, int a, int b); // Вырезать из выпуклой оболочки точки начиная с индекса a и заканчивая индексом b
  33.  
  34. // Вывод вектора из точек:
  35. void print_points(const Points & points); // Вывод на экран
  36. void printf_points(const Points & points, const std::string & filename); // Вывод в файл
  37.  
  38. // Работа с числами в кольце по модулю n:
  39. void to_n(int & val, int n); // Приводит значение val в диапазон значений [0, .., n-1]
  40. void inc_n(int & val, int n, int k = 1); // Увеличивает значение val на k (по умолчанию: k=1) и вычисляет новое значение в кольце по модулю n
  41. void dec_n(int & val, int n, int k = 1); // Уменьшает значение val на k (по умолчанию: k=1) и вычисляет новое значение в кольце по модулю n
  42.  
  43. // Работа с отдельными точками:
  44. double dist(const Point & A, const Point & B); // Вычисление расстояния между точками A и B
  45. double det(const Point & A, const Point & B); // Вычисление определителя, первая строка которого состоит из координат точки A, вторая из координат точки B
  46. double square_triangle(const Point & A, const Point & B, const Point & C); // Вычисление ориентированной площади треугольника с вершинами A, B и C
  47. bool in_interval(const Point & A, const Point & B, const Point & C); // Определение, лежит ли точка C на отрезке AB
  48.  
  49.  
  50. void create_convex_hull_from_file(Points & hull, const std::string & filename){
  51. //  Создание выпуклой оболочки из файла. Точки поступают последовательно
  52.     if (!hull.empty()) hull.clear();
  53.    
  54.     std::ifstream fin(filename); // Открываем файл для чления
  55.     if (!fin) return; // Если файл не открыт, выходим
  56.    
  57.     TypeOfPoint x, y;
  58.     while(fin>>x>>y)
  59.         add_to_convex_hull(hull, Point(x, y));
  60. }
  61.  
  62. void create_convex_hull_from_points(Points & hull, const Points & points){
  63. //  Создание выпуклой оболочки из вектора точек на плоскости
  64.  
  65. //  Очистим оболочку, если она не пуста:
  66.     if (!hull.empty()) hull.clear();
  67.  
  68. //  По одной точке добавляем в выпуклую оболочку:
  69.     for(int i = 0, n = points.size(); i < n; i++)
  70.         add_to_convex_hull(hull, points[i]);
  71. }
  72.  
  73. void add_to_convex_hull(Points & hull, const Point & p){
  74. //  Добавление точки к уже существующей выпуклой оболочке
  75.  
  76. //  Выбираем случай, исходя из размера оболочки:
  77.     switch(hull.size()){
  78.         case 0:  add_to_zero_convex_hull(hull, p);  break; // Случай 1: оболочка пуста
  79.         case 1:  add_to_one_convex_hull(hull, p);   break; // Случай 2: оболочка состоит из одной точки
  80.         case 2:  add_to_two_convex_hull(hull, p);   break; // Случай 3: оболочка состоит из двух точек
  81.         default: add_to_other_convex_hull(hull, p); break; // Случай 4: оболочка состоит более чем из двух точек
  82.     }
  83. }
  84.  
  85. void add_to_zero_convex_hull(Points & hull, const Point & p){
  86. //  Добавление указателя на точку к пустой выпуклой оболочке
  87.     hull.push_back(p);
  88. }
  89.  
  90. void add_to_one_convex_hull(Points & hull, const Point & p){
  91. //  Добавление указателя на точку к выпуклой оболочке, состоящей из одной точки
  92.  
  93. //  Сравниваем уже имеющуюся точку с новой точкой. Если не совпадают, то добавляем
  94.     if (hull[0] != p) hull.push_back(p);
  95. }
  96.  
  97. void add_to_two_convex_hull(Points & hull, const Point & p){
  98. //  Добавление указателя на точку к выпуклой оболочке, состоящей из двух точек
  99.  
  100. //  Считаем ориентированную площадь треугольника, состоящего из точек в оболочке и новой точки
  101.     double sq = square_triangle(hull[0], hull[1], p);
  102.  
  103. //  Если площадь равна нулю, то проверяем, лежит ли новая точка на отрезке, соединяющем две точки выпуклой оболочки
  104.     if (sq == 0 ){
  105.         if (!in_interval(hull[0], hull[1], p)) // Если новая точка не лежит на отрезке, но ориентированная площадь равна нулю
  106.             if (dist(hull[0], p) > dist(hull[1], p)) // Если расстояние от новой точки до нулевой больше, чем от новой до первой
  107.                 hull[1] = p; // То заменяем первую точку на новую
  108.             else // Иначе
  109.                 hull[0] = p; // Заменяем вторую точку на новую
  110.         return; // Выходим из функции
  111.     }
  112.  
  113. //  Добавляем точку в конец
  114.     hull.push_back(p);
  115.  
  116. //  Если значение ориентированной площади отрицательно, то меняем местами точку вначале и вконце.
  117. //  Это позволяет сохранить направление обхода против часовой стрелки.
  118.     if (sq < 0) swap(hull[0], hull[2]);
  119. }
  120.  
  121. void add_to_other_convex_hull(Points & hull, const Point & p){
  122. //  Добавляет новую точку к выпуклой оболочке, состоящей больше, чем из двух точек
  123.  
  124. //  Создаем два отрезка для хранения цепей в оболочке, которые необходимо модифицировать
  125.     std::pair<int, int> interval[] = {std::pair<int, int>(-1, -1), std::pair<int, int>(-1, -1)};
  126.     int n = hull.size(); // Размер оболочки
  127.     int cur = 0; // Индекс текущего отрезка
  128.     for(int i = 0; i < n; i++){
  129. //      Вычислим индекс j точки, следующей сразу за текущей точкой:
  130.         int j = i + 1;
  131.         to_n(j, n);
  132.  
  133. //      Посчитаем ориентированную площадь треугольника, образованного точками hull[i], p, hull[j]
  134.         double sq = square_triangle(hull[i], p, hull[j]);
  135.         if (sq == 0 && in_interval(hull[i], hull[j], p)) // Если она равна нулю и точка лежит на отрезке между точками hull[i] и hull[j]
  136.             return; // То выходим из функции
  137.  
  138. //      С этого момента мы считаем, что отрезок [i, j] выпуклой оболочки необходимо модифицировать
  139.  
  140.         if (sq >= 0){ // Если значение площади не отрицательно
  141. //          Если еще не задан текущий отрезок, который нам нужно модифицировать в выпуклой оболочке
  142.             if (interval[cur].first == interval[cur].second)
  143.                 interval[cur] = std::pair<int, int>(i, j); // То задаем его
  144.             else if (interval[cur].second == i) // Иначе, если конец текущего отрезка совпадает с началом полученного отрезка [i, j]
  145.                 interval[cur].second = j; // Концом текущего отрезка становится конец полученного (то есть к концу текущего отрезка прибавляется полученный)
  146.             else if (interval[cur].first == j) // Иначе, если начало текущего отрезка совпадает с концом полученного отрезка [i, j]
  147.                 interval[cur].first = i; // Началом текущего отрезка становится начало полученнго (то есть к началу текущего отрезка прибавляется полученный)
  148.             else // Иначе, полученный отрезок [i, j] становится вторым отрезком, который необходимо модифицировать, и сразу же текущим
  149.                 interval[++cur] = std::pair<int, int>(i, j);
  150.         }
  151.     }
  152.  
  153. //  На выходе мы имеем два отрезка, которые необходимо соединить в один, если совпадают конец второго и начало первого:
  154.     if (interval[1].second == interval[0].first)
  155.         interval[0].first = interval[1].first;
  156.  
  157. //  Теперь мы будем рассматривать один целый отрезок [begin, end]
  158.     int begin = interval[0].first;
  159.     int end = interval[0].second;
  160.  
  161. //  Вычисляем его длину по модулю n:
  162.     int len = (end - begin);
  163.     to_n(len, n);
  164.  
  165.     if (begin > end && end != 0) { // Если начало отрезка больше конца
  166.         shift_points_left(hull, end); // То сдвигаем все элементы оболочки влево на величину, равную концу отрезка
  167.         begin -= end;
  168.         end -= end;
  169.     }
  170.  
  171. //  Крайние точки мы должны оставить в покое, поэтому:
  172.     inc_n(begin, n); // Увеличиваем на единицу начало отрезка
  173.     dec_n(end, n); // Уменьшаем на единицу конец отрезка
  174.  
  175.     switch (len){ // В зависимости от длины отрезка
  176.         case 0: return; // Ничего не делаем, если длина отрезка равна 0
  177.         case 1: hull.emplace(hull.begin()+begin, p); break; // Вставляем новую точку, если длина отрезка равна 1
  178.         case 2: hull[begin] = p; break; // Заменяем точку, расположенную вначале отрезка, на новую точку
  179.         default:
  180.             hull[begin] = p; // Заменяем точку вначале отрезка на новую точку
  181.             inc_n(begin, n); // Увеличиваем начало отрезка
  182.             erase_from_points(hull, begin, end); // Вырезаем все остальные точки из отрезка [begin, end]
  183.             break;
  184.     }
  185. }
  186.  
  187. void shift_points_left(Points & points, int k){
  188. //  Сдвигает все элементы выпуклой оболочки на k позиций влево
  189.     reverse(points.begin(), points.begin()+k-1);
  190.     reverse(points.begin()+k, points.end());
  191.     reverse(points.begin(), points.end());
  192. }
  193.  
  194. void shift_points_right(Points & points, int k){
  195. //  Сдвигает все элементы выпуклой оболочки на k позиций вправо
  196.     shift_points_left(points, points.size()-k+1);
  197. }
  198.  
  199. void erase_from_points(Points & points, int a, int b){
  200. //  Вырезает из выпуклой оболочки точки с позиции a до позиции b включительно
  201.     int n = points.size();
  202.  
  203.     to_n(a, n); // Вычисляем a по модулю n
  204.     to_n(b, n); // Вычисляем b по модулю n
  205.  
  206.     if (a > b) return; // Если левый конец отрезка больше правого, то выходим из функции
  207.  
  208.     if (a == b) // Если начало совпадает с концом, то нам нужно вырезать только один элемент
  209.         points.erase(points.begin()+a);
  210.     else // Иначе вырезаем весь отрезок
  211.         points.erase(points.begin()+a, points.begin()+b+1);
  212. }
  213.  
  214. void printf_points(const Points & points, const std::string & filename){
  215. //  Вывод в файл выпуклой оболочки, состоящей из указателей на точки
  216.     std::ofstream fout(filename);
  217.     for(int i = 0, n = points.size(); i < n; i++)
  218.         fout<<points[i].first<<" "<<points[i].second<<std::endl;
  219. }
  220.  
  221. void print_points(const Points & points){
  222. //  Вывод на экран вектора из точек
  223.     for(int i = 0, n = points.size(); i < n; i++)
  224.         std::cout<<"("<<points[i].first<<";"<<points[i].second<<") ";
  225.     std::cout<<std::endl;
  226. }
  227.  
  228. void to_n(int & val, int n){
  229. //  Функция вычисляет значение val по модулю n
  230.     if (val >= n)
  231.         val %= n;
  232.     else if (val < 0)
  233.         val = n - abs(val) % n;
  234. }
  235.  
  236. void inc_n(int & val, int n, int k){
  237. //  Функция увеличивает значение val, а затем вычисляет его по модулю n
  238.     val += k;
  239.     to_n(val, n);
  240. }
  241.  
  242. void dec_n(int & val, int n, int k){
  243. //  Функция уменьшает значение val, а затем вычисляет его по модулю n
  244.     val -= k;
  245.     to_n(val, n);
  246. }
  247.  
  248. double det(const Point & A, const Point & B){
  249. //  Подсчет определителя, первой строкой которого идут координаты точки a, второй строкой - координаты точки b
  250.     return (A.first * B.second - B.first * A.second);
  251. }
  252.  
  253. double square_triangle(const Point & A, const Point & B, const Point & C){
  254. //  Подсчет ориентированной площади треугольника, состоящего из вершин a, b и c
  255.     return ((det(A,B)+det(B,C)+det(C,A))/2.0);
  256. }
  257.  
  258. double dist(const Point & A, const Point & B){
  259. //  Вычисляет расстояние от точки A до точки B
  260.  
  261. //  Вычтем точку A из точки B:
  262.     Point C(B.first - A.first, B.second - A.second);
  263.  
  264. //  Возводим каждую компоненту в квадрат, суммируем и извлекаем корень:
  265.     return (sqrt(C.first*C.first+C.second*C.second));
  266. }
  267.  
  268. bool in_interval(const Point & A, const Point & B, const Point & C){
  269. //  Проверяет, принадлежит ли точка C отрезку AB
  270. //  Точка C точно не принадлежит этому отрезку, если сумма расстояний от его концов до точки C больше длины отрезка
  271.  
  272. //  Вычисляем длины отрезков AC, BC и AB:
  273.     double AC = dist(A, C);
  274.     double BC = dist(B, C);
  275.     double AB = dist(A, B);
  276.  
  277. //  Проверяем условие принадлежности:
  278.     return (AC + BC == AB);
  279. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement