Пример кода

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;

namespace RocketMap.Common.Clusterization
{
    public class Clusterizer<T>
    {
        private readonly Func<Point, Point, bool> _isPointsInSameCluster;
        private readonly List<Cluster<T>> _clusters = new List<Cluster<T>>();

        private readonly List<Node<T>> _nodes = new List<Node<T>>();

        private readonly List<Node<T>> _lonelyNodes = new List<Node<T>>();

        public IEnumerable<Cluster<T>> Clusters {get { return _clusters; }}

        /// <summary>
        /// Узлы которые не попадают в кластеры
        /// </summary>
        public IEnumerable<Node<T>> LonelyNodes {get { return _lonelyNodes; }}


        /// <summary>
        /// Создает новый экземпляр кластеризатора
        /// </summary>
        /// <param name="items">Объекты которые нужно кластеризовать</param>
        /// <param name="pointFieldMapping">Функция, по объекту получающая его местоположение</param>
        /// <param name="isPointsInSameCluster">Функция определяющая принадлежат ли две точки к одному кластеру</param>
        /// <param name="skipCondition">Фукция фильтрации. Если возвращает true, то объект исключается из кластеризации и попадает сразу в коллекцию LonelyNodes</param>
        public Clusterizer(IEnumerable<T> items, Func<T, Point> pointFieldMapping, Func<Point, Point, bool> isPointsInSameCluster, Func<T, bool> skipCondition)
        {
            _isPointsInSameCluster = isPointsInSameCluster;

            foreach (var item in items)
            {
                if (!skipCondition(item))
                {
                    var node = new Node<T>(pointFieldMapping(item), item);
                    _nodes.Add(node);
                    //для каждого нового узла создаем кластер
                    _clusters.Add(new Cluster<T>(node));
                }
                else
                    _lonelyNodes.Add(new Node<T>(pointFieldMapping(item), item));
            }
            Clusterize();
        }

        protected void Clusterize()
        {
            //объединяем кластеры до тех пор пока все ни окажутся на достаточно удаленном друг от друга расстоянии
            MergeNearestClusters();

            //удаляем все кластеры,в которых только одна точка и переносим эти точки в список LonelyNodes
            RemoveOneNodeClusters();
        }

        private void MergeNearestClusters()
        {
            var clustersToMerge = _clusters.ToList();
            var step = 0;
            var reduce = 0;

            do
            {
                if (clustersToMerge.Count == 0)
                {
                    break;
                }

                var cluster = clustersToMerge[0];
                reduce += TryToMergeCluster(cluster, clustersToMerge);
                step++;

                if (step <= clustersToMerge.Count) continue;
                
                if (reduce == 0) break;
                step = 0;
                reduce = 0;

            } while (_clusters.Any());
        }

        /// <summary>
        /// Объединить кластеры, если они перекрывают друг дуга
        /// </summary>
        private int TryToMergeCluster(Cluster<T> cluster, List<Cluster<T>> clustersToMerge)
        {
            var mergable = _clusters.Where(x => cluster != x && _isPointsInSameCluster(x.Point, cluster.Point)).ToArray();

            foreach (var targetCluster in mergable)
            {
                cluster.Merge(targetCluster);
                _clusters.Remove(targetCluster);
                clustersToMerge.Remove(targetCluster);
            }
            clustersToMerge.Remove(cluster);
            clustersToMerge.Add(cluster);

            return mergable.Count();
        }

        private void RemoveOneNodeClusters()
        {
            foreach (var cluster in _clusters.Where(x => x.Count == 1).ToArray())
            {
                _lonelyNodes.Add(cluster[0]);
                _clusters.Remove(cluster);
            }
        }
    }
}