Ответ 1
В случае, когда вы никогда не добавляете объекты самореференции (для определения позже), ваша структура данных описывает направленный ациклический граф (http://en.wikipedia.org/wiki/Directed_acyclic_graph), где каждый экземпляр класса IAmCircular описывает node с набором прямых узлов-потомков = Дети.
Предполагая предварительное условие, что до этого момента цикл не был создан, функция, которую вы хотите, PerformCircularReferenceCheck, нуждается только в том, чтобы проверить, доступен ли "this" от "target". Если это так, он должен вернуть исключение.
Теория сложности мудрая, эта проблема является ST-связностью (http://en.wikipedia.org/wiki/St-connectivity) и является полной для класса NL (http://en.wikipedia.org/wiki/NL_(complexity)), даже если вы ограничиваете ввод ациклическими графами (это ваш случай).
В частности, теорема Саввича (http://en.wikipedia.org/wiki/Savitch%27s_theorem) дает конструктивный способ создания O (log ^ 2 n) пространственного алгоритма ( работающий во времени O (n ^ 2)) для него, где n - количество узлов.
Кроме того, будучи NL-полным, маловероятно, что существует алгоритм, который выполняется в пространстве O (log n) (т.е. использует только постоянное число указателей на узлы), поскольку это будет означать маловероятное NL = L. EDIT: в частности, небольшие вариации заяц-заяц и черепахи, предложенные кем-то, не будут работать (потому что они будут использовать слишком мало места).
Я бы рекомендовал реализовать тривиальный алгоритм O (n) времени O (n), который генерирует для "target" его набор преемников (рекурсивно) и проверяет, появляется ли в этом наборе "this".
Будьте осторожны, важна явная конструкция набора. В противном случае, если вы просто рекурсивно проверяете, доступен ли "this" любым преемником "цели", вы рискуете запустить его в экспоненциальном времени.
Я рекомендовал O (n) time/O (n) пространственный алгоритм, потому что асимптотически лучше, чем вы можете делать по времени, и вы уже используете O (n) пространство для своей структуры данных.