Кажется, моя перевернута и начинается справа налево. Вы знаете, что вызывает это?
Как уже отмечали другие, вы нажимаете узлы для посещения в стеке в порядке слева направо. Это означает, что они выталкиваются справа налево, поскольку стек имеет обратный порядок. Стеки действуют в порядке поступления.
Вы можете решить эту проблему, заставив GetConnectedVertices создавать стек, а не список. Таким образом, соединенные вершины переворачиваются дважды: один раз, когда они попадают в возвращаемый стек, и один раз, когда они попадают в реальный стек.
Также буду очень признателен за любые советы по моей реализации
Реализация работает, я полагаю, но у нее очень много фундаментальных проблем. Если бы мне представили этот код на проверку, я бы сказал следующее:
Во-первых, предположим, что вы хотите выполнить два поиска в глубину этой структуры данных одновременно. Либо потому, что вы делали это в нескольких потоках, либо потому, что у вас есть вложенный цикл, в котором внутренний цикл выполняет DFS для другого элемента, чем внешний цикл. Что просходит? Они мешают друг другу, потому что оба пытаются изменить поля "State" и "VisitNumber". Это действительно плохая идея иметь то, что должно быть «чистой» операцией, такой как поиск, на самом деле делает вашу структуру данных «грязной».
Это также делает невозможным использование постоянных неизменяемых данных для представления избыточных частей графика.
Кроме того, я заметил, что вы опускаете очищающий код. Когда «Состояние» когда-либо возвращалось к исходному значению? Что, если вы выполнили вторую DFS? Это немедленно потерпит неудачу, так как корень уже посещен.
По всем этим причинам лучшим выбором будет сохранение «посещенного» состояния в отдельном объекте, а не в каждой вершине.
Далее, почему все объекты состояния являются частными переменными класса? Это простой алгоритм; нет необходимости создавать для него целый класс. Алгоритм поиска в глубину должен использовать граф для поиска как формальный параметр, а не как состояние объекта, и он должен поддерживать свое собственное локальное состояние по мере необходимости в локальных переменных, а не в полях.
Далее, абстракция графа... ну, это не абстракция. Это два списка, один из вершин и один из ребер. Откуда мы знаем, что эти два списка хотя бы непротиворечивы? Предположим, что есть вершины, которых нет в списке вершин, но которые есть в списке ребер. Как это предотвратить? Вам нужна абстракция графа. Пусть реализация абстракции графа заботится о том, как представлять ребра и находить соседей.
Далее, ваше использование ForEach является и законным, и обычным, но это вызывает у меня головную боль. Трудно читать ваш код и рассуждать об этом со всеми лямбда-выражениями. У нас есть прекрасное выражение foreach. Используй это.
Затем вы изменяете «родительское» свойство, но совершенно не ясно, для чего это свойство или почему оно изменяется во время обхода. Вершины в произвольном графе не имеют «родителей», если граф не является деревом, а если граф является деревом, то нет необходимости отслеживать «посещенное» состояние; в дереве нет петель. Что здесь происходит? Этот код просто странный, и нет необходимости выполнять DFS.
Далее, ваш вспомогательный метод с именем GetConnectedVertices является ложью. Он не получает связанные вершины, он получает связанные еще не посещенные вершины. Методы, названия которых лгут, очень сбивают с толку.
Наконец, заявлено, что это поиск в глубину, но он ничего не ищет! Где искомая вещь? Куда возвращается результат? Это вообще не поиск, это обход.
Начать сначала. Что ты хочешь? Обход графа в глубину с заданной начальной вершиной. Затем реализуйте это. Начните с определения того, что вы пересекаете. График. Какая услуга вам нужна от графа? Способ получения множества соседних вершин:
interface IGraph
{
IEnumerable<Vertex> GetNeighbours(Vertex v);
}
Что возвращает ваш метод? Последовательность вершин в порядке глубины. Что для этого потребуется? Начальная вершина. OK:
static class Extensions
{
public static IEnumerable<Vertex> DepthFirstTraversal(
this IGraph graph,
Vertex start)
{ ... }
}
Теперь у нас есть тривиальная реализация поиска в глубину; теперь вы можете использовать предложение Where:
IGraph myGraph = whatever;
Vertex start = whatever;
Vertex result = myGraph.DepthFirstTraversal(start)
.Where(v=>something)
.FirstOrDefault();
Итак, как мы собираемся реализовать этот метод, чтобы он выполнял обход, не разрушая состояние графа? Поддерживайте собственное внешнее состояние:
public static IEnumerable<Vertex> DepthFirstTraversal(
this IGraph graph,
Vertex start)
{
var visited = new HashSet<Vertex>();
var stack = new Stack<Vertex>();
stack.Push(start);
while(stack.Count != 0)
{
var current = stack.Pop();
if(!visited.Add(current))
continue;
yield return current;
var neighbours = graph.GetNeighbours(current)
.Where(n=>!visited.Contains(n));
// If you don't care about the left-to-right order, remove the Reverse
foreach(var neighbour in neighbours.Reverse())
stack.Push(neighbour);
}
}
Видите, насколько это чище и короче? Отсутствие мутации состояния. Никаких возни со списками краев. Нет плохо названных вспомогательных функций. И код на самом деле делает то, о чем говорит: обходит граф.
Мы также получаем преимущества блоков итераторов; а именно, если кто-то использует это для поиска DF, то итерация прекращается, когда критерии поиска удовлетворяются. Нам не нужно делать полный обход, если мы находим результат раньше.
27.04.2011
Add
и, если оно не новое, продолжаю. т.е.if (!visited.Add(current)) continue; yield return current;
20.01.2014var neighbours = graph.GetNeighbours(current) .Where(n=>!visited.Contains(n));
Особенно.Where(n=>!visited.Contains(n))
. 22.12.2017