Russian Qt Forum
Май 15, 2024, 12:19 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Выделить кластеры  (Прочитано 5587 раз)
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« : Март 02, 2012, 12:56 »

Добрый день

Есть контейнер и есть ф-ция принимающая 2 элемента и возвращающая true если они "связаны" напр
Код
C++ (Qt)
bool Linked( const Point & p0, const Point & p1 );
 
Отношение связи не транзитивно, напр
Код
C++ (Qt)
Linked(p0, p1) = true;
Linked(p1, p2) = true;
Linked(p0, p2) = false;
 

Нужно разделить исходный контейнер на какое-то число (как получится) новых контейнеров (не удаляя и не создавая новых элементов) так чтобы выполнялись условия:

- для любых 2 элементов из разных контейнеров Linked вернет false
- для любого элемента в его контейнере найдется хотя бы один с которым Linked вернет true

Пример: в исходном контейнере точки принадлежащие кубу. Ф-ция Linked сравнивает угол между нормалями, результат - 6 новых контейнеров, в каждом из которых точки на 1 грани куба. А вот если бы точки принадлежали сфере (вместо куба) то вероятно ничего бы не поделилось

Можно ли это сделать без унылого перебора - ну или хоть как-то ускорить?

Спасибо

Записан
Tonal
Гость
« Ответ #1 : Март 05, 2012, 12:23 »

Это более точно называется нахождением связности.
Детально описано в первой главе первого тома Р. Седжвика «Фундаментальные алгоритмы на С++».
Вот на хабре статейка: http://habrahabr.ru/blogs/algorithm/104772/
Записан
Tonal
Гость
« Ответ #2 : Март 06, 2012, 09:39 »

Ну да при условии, что все элементы различны мы можем написать быстрый алгоритм связности без использования деревьев - только несколько списочков - очередей. Улыбающийся
Идея в том, что как если экземпляр попал в какое-то множество (контейнер) его не нужно тестировать (вызывать Linked) с остальными членами этого множества.
Код для иллюстрации:
Код
Haskell
import Data.List as DL
 
-- cohesion принимает функцию группировки (Linked) и список элементов, возвращает список групп элементов
cohesion :: (a -> a -> Bool) ->[a] -> [[a]]
-- тривиальные случаи
cohesion _ [] = []
cohesion _ [x] = [[x]]
cohesion test [x, y]
 | test x y == True = [[x, y]]
 | otherwise = [[x], [y]]
-- общий алгоритм - выделяем очередную группу по первому элементу и остаток, над остатком проделываем то же самое.
cohesion test (x:xs) = (x:as_x) : cohesion test not_as_x
 where
   (asx, other) = DL.partition (test x) xs
   (as_x, not_as_x) = collect asx other
   collect [] not_as_x' = ([], not_as_x')
   collect asx' [] = (asx', [])
   collect (y:ys) zs = (y:as_y, not_as_y)
     where
       (asy, ozs) = DL.partition (test y) zs
       (as_y, not_as_y) = collect (ys++asy) ozs
 
-- Ну и протестируем на нескольких списках и функциях
main :: IO ()
main = do
 -- всегда False, групп столько же, сколько элементов
 putStrLn $ show $ cohesion (\_ _ -> False) [1..5]
 -- всегда True одна группа со всеми элементами
 putStrLn $ show $ cohesion (\_ _ -> True) [1..5]
 -- выделяем группу элементов <= 3  и группу > 7
 putStrLn $ show $ cohesion (\a b -> (a <= 3 && b <= 3) || (a > 7 && b > 7)) [1..10]
 
Компиляция и запуск:
Код:
$ ghc Cohesion.hs
[1 of 1] Compiling Main             ( Cohesion.hs, Cohesion.o )
Linking Cohesion ...

$ ./Cohesion
[[1],[2],[3],[4],[5]]
[[1,2,3,4,5]]
[[1,2,3],[4],[5],[6],[7],[8,9,10]]
« Последнее редактирование: Март 06, 2012, 09:51 от Tonal » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #3 : Март 06, 2012, 13:03 »

Это более точно называется нахождением связности.
Детально описано в первой главе первого тома Р. Седжвика «Фундаментальные алгоритмы на С++».
Вот на хабре статейка: http://habrahabr.ru/blogs/algorithm/104772/
Фундаментальную теорию пока не читал. Статейку посмотрел но пока не очень понял - ну это нормально, спасибо

Идея в том, что как если экземпляр попал в какое-то множество (контейнер) его не нужно тестировать (вызывать Linked) с остальными членами этого множества.
Это понятно. Как сделано сейчас (псевдокод)

Код
C++ (Qt)
bool CanAdd( const Point & pt, const vector <CPoint> & c )
{
for (size_t i = 0; i < c.size(); ++i)
 if (Linked(pt, c[i]) return true;
return false;
}
 
...
vector <vector<CPoint> > data; // вектор новых контейнеров
 
for (size_t i = 0; i < src.size(); ++i)  { // scr - исходный контейнер
size_t index;
const Point & pt = src[i];
for (index = 0; i < data.size(); ++i)   // ищем контейнер куда можно добавить
 if (CanAdd(pt, data[i])) break;
 
if (index >= data.size()) {   // не нашли, создаем новый контейнер
 data.push_back(vector <CPoint> ());
 data.back().push_back(pt);
}
 
// нашли подходящий
else {
  vector <CPoint> & dst = data[index];
  dst.push_back(pt);  // добавили в него
 
// теперь все равно надо проверить оставшиеся
 size_t  index2 = index + 1;  
 while (index2 < data.size()) {
  vector <CPoint> & sec = data[index2];
  if (!CanAdd(pt, sec)) ++index2;
  else {  // сливаем
   dst.insert(dst.end(), src.begin(), sec.end());
   data.erase(data.begin() + index2);
  }
 }
}
}
 
Ну используются списки (вместо векторов) но суть та же. Пока не вижу причем здесь дерево и как ускориться
« Последнее редактирование: Март 06, 2012, 13:07 от Igors » Записан
Tonal
Гость
« Ответ #4 : Март 07, 2012, 09:03 »

Контейнер, куда добавлять можно вовсе не искать.

Мы берём первый элемент x и делим оставшиеся на тех кто входит в группу "таких как x" (текущая группа) и остаток.
Далее, для каждого элемента из текущей группы повторяем с остатком то же самое добавляя новые элементы в текущую группу и уменьшая остаток.
Как только все все элементы из текущей группы обработаны - переходим в начало. Улыбающийся
Таким образом, мы всегда знаем контейнер в который добавляем.

Вот это в коде, смотри:
Код
Haskell
-- cohesion принимает функцию группировки принимающую 2 элемента и возвращающую Bool (Linked) и список элементов
-- возвращает список групп элементов
cohesion :: (a -> a -> Bool) ->[a] -> [[a]]
 
-- Запись x:xs обозначает список из головы x и хвоста xs. Хвост может быть пустой
-- Запись test x обозначает что мы зафиксировали первый параметр функции. Теперь при вызове ей нужен только второй параметр.
-- Запись xs++ys для списков, обозначает конкатенацию списков - список ys добавляется в конец списка xs
 
-- для входного списка с головой x и хвостом xs возвращаем список из
cohesion test (x:xs) = (x:as_x) : cohesion test not_as_x
 where
   -- делим хвост списка на тех, кто "как x" (asx) и остальных (other), вызывая для каждого test с x-ом в первом параметре
   (asx, other) = DL.partition (test x) xs
   -- из остальных выбираем тех которые походят на какой-нибудь элемент из "как x" и остаток
   (as_x, not_as_x) = collect asx other
   -- тривиальные случаи
   collect [] not_as_x' = ([], not_as_x') -- список "как x" пустой -
   collect asx' [] = (asx', []) -- список остальных пустой
   collect (y:ys) zs = (y:as_y, not_as_y)
     where
       -- делим остальных на таких как голова тестируемого списка и не таких (ozs)
       (asy, ozs) = DL.partition (test y) zs
       -- для остатка списка и вновь прибывших проводим разделение
       (as_y, not_as_y) = collect (ys++asy) ozs
 
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #5 : Март 09, 2012, 12:49 »

Мы берём первый элемент x и делим оставшиеся на тех кто входит в группу "таких как x" (текущая группа) и остаток.
Далее, для каждого элемента из текущей группы повторяем с остатком то же самое добавляя новые элементы в текущую группу и уменьшая остаток.
Как только все все элементы из текущей группы обработаны - переходим в начало. Улыбающийся
Таким образом, мы всегда знаем контейнер в который добавляем.
Ну и что мы выиграли? Пусть простейший случай: точки принадлежат 2 граням куба. Начали очень хорошо - напр первая тысяча точек вся на первой грани. Добавили в первый выходной контейнер. И вот теперь попалась точка на второй грани. Сделали тысячу проверок - никак в первый контейнер не добавить, создаем второй. Следующая точка опять на второй грани. Да, мы может и быстро добавили ее во второй контейнер, НО никто не обещал что она не может входить и в первый - опять полетела тысяча проверок, вот где тормоза. Если я что-то не понял - поясните.
Записан
Tonal
Гость
« Ответ #6 : Март 11, 2012, 10:19 »

1 .Берём первую точку и добавляем её в выходной первый контейнер.
Теперь тестируем её со всеми точками и добавляем в этот же контейнер всю 1000 с первой грани.

2. Далее, берём какую-нибудь точку добавленную в выходной контейнер (но не первую).
Её уже не нужно тестировать не только с первой, но и с остальными 998-ю точками контейнера
Стало быть тестируем её и точки не попавшие в контейнер - если кого-то нашли добавляем в выходной контейнер.

3. Опять берём ещё не обработанную точку из выходного контейнера и проделываем то же самое, что со второй.

4. Когда у нас будет обработан таким образом весь выходной контейнер, в остатке не останится ни одной точки которая могла бы к нему принадлежать.
Тут мы или заканчиваем работу - если в остатке ничего нет, или возвращаемся к шагну 1. Улыбающийся
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.125 секунд. Запросов: 22.