Реализация поиска в глубину в C # с использованием списка и стека - PullRequest
28 голосов
/ 27 апреля 2011

Я хочу создать поиск в глубину, в котором я был несколько успешен.

Вот мой код (за исключением моего конструктора, обратите внимание, что классы Vertex и Edge содержат только свойства, ничего важного для публикации здесь):

private Stack<Vertex> workerStack = new Stack<Vertex>();
private List<Vertex> vertices = new List<Vertex>();
private List<Edge> edges = new List<Edge>();

private int numberOfVertices;
private int numberOfClosedVertices;
private int visitNumber = 1;

private void StartSearch()
{
    // Make sure to visit all vertices
    while (numberOfClosedVertices < numberOfVertices && workerStack.Count > 0)
    {
        // Get top element in stack and mark it as visited
        Vertex workingVertex = workerStack.Pop();
        workingVertex.State = State.Visited;

        workingVertex.VisitNumber = visitNumber;
        visitNumber++;

        numberOfClosedVertices++;

        // Get all edges connected to the working vertex
        foreach (Vertex vertex in GetConnectedVertices(workingVertex))
        {
            vertex.Parent = workingVertex;
            workerStack.Push(vertex);
        }
    }
}

private List<Vertex> GetConnectedVertices(Vertex vertex)
{
    List<Vertex> vertices = new List<Vertex>();

    // Get all vertices connected to vertex and is unvisited, then add them to the vertices list
    edges.FindAll(edge => edge.VertexSource == vertex && edge.VertexTarget.State == State.Unvisited).ForEach(edge => vertices.Add(edge.VertexTarget));

    return vertices;
}

Он работает так, что все вершины посещаются, но не в правильном порядке.

Вот сравнение того, как мои посещают по сравнению с Википедией: Comparison

Кажется, что мой повернут и начинается справа налево.

Вы знаете, что вызывает это? (Также любые советы по моей реализации будут с благодарностью)

Спасибо

РЕДАКТИРОВАТЬ: я получил свой ответ, но все еще хотел показать конечный результат для метода GetConnectedVertices:

private List<Vertex> GetConnectedVertices(Vertex vertex)
{
    List<Vertex> connectingVertices = new List<Vertex>();

    (from edge in edges
     where edge.VertexSource == vertex && edge.VertexTarget.State == State.Unvisited
     select edge).
     Reverse().
     ToList().
     ForEach(edge => connectingVertices.Add(edge.VertexTarget));

    return connectingVertices;
}

Ответы [ 6 ]

54 голосов
/ 27 апреля 2011

Кажется, моя развернута и начинается справа налево.Вы знаете, что вызывает это?

Как уже отмечали другие, вы перемещаете узлы к следующему посещению в стеке в порядке слева направо.Это означает, что они выталкиваются справа налево, так как стек меняет порядок.Стеки являются последними в первых.

Вы можете решить эту проблему, заставив GetConnectedVertices создать стек, а не список.Таким образом, связанные вершины меняются местами дважды , один раз, когда они идут в возвращенный стек, и один раз, когда они идут в реальный стек.

Также любые советы по моей реализации будутс благодарностью

Реализация работает, я полагаю, но у нее очень много фундаментальных проблем.Если бы мне представили этот код для проверки, вот что я бы сказал:

Прежде всего, предположим, что вы хотите выполнить два поиска в глубину этой структуры данных одновременно.Либо потому, что вы делали это в нескольких потоках, либо потому, что у вас есть вложенный цикл, в котором внутренний цикл выполняет DFS для элемента, отличного от внешнего цикла.Что просходит?Они мешают друг другу, потому что оба пытаются изменить поля «State» и «VisitNumber».Это действительно плохая идея - иметь то, что должно быть «чистой» операцией, такой как поиск, на самом деле делает вашу структуру данных «грязной».

Это также делает невозможным использование постоянных неизменяемых данных для представления избыточных частей вашего графика.

Кроме того, я замечаю, что вы пропускаете очищающий код.Когда «State» когда-либо возвращается к своему первоначальному значению?Что делать, если вы сделали секунду 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, то итерация прекращается, когда критерии поиска удовлетворяются. Нам не нужно делать полный обход, если мы находим результат рано.

4 голосов
/ 21 сентября 2012

Я обобщил код @ Эрика для обхода DFS для любого T, чтобы заставить вещи работать для любого типа, у которого есть дети - я думал, что поделюсь:

public static IEnumerable<T> DepthFirstTraversal<T>(
    T start,
    Func<T, IEnumerable<T>> getNeighbours)
{
    var visited = new HashSet<T>();
    var stack = new Stack<T>();
    stack.Push(start);

    while (stack.Count != 0)
    {
        var current = stack.Pop();
        visited.Add(current);
        yield return current;

        var neighbours = getNeighbours(current).Where(node => !visited.Contains(node));

        // If you don't care about the left-to-right order, remove the Reverse
        foreach(var neighbour in neighbours.Reverse())
        {
            stack.Push(neighbour);
        }
    }
}

Пример использования:

var nodes = DepthFirstTraversal(myNode, n => n.Neighbours);
1 голос
/ 27 апреля 2011

Проблема в порядке поиска элементов.Ваш for each в StartSearch не гарантирует порядок элементов.Вы также не FindAll в GetConnectedVertices метод.Давайте посмотрим на эту строку:

edges.FindAll(edge => edge.VertexSource == vertex && edge.VertexTarget.State == State.Unvisited).ForEach(edge => vertices.Add(edge.VertexTarget));

Вы должны добавить OrderBy(), чтобы обеспечить желаемый заказ.

0 голосов
/ 18 июня 2015

Это моя реализация, достаточно одного стека. Обратное выполняется перед циклом foreach.

    /// <summary>
    /// Depth first search implementation in c#
    /// </summary>
    /// <typeparam name="T">Type of tree structure item</typeparam>
    /// <typeparam name="TChilds">Type of childs collection</typeparam>
    /// <param name="node">Starting node to search</param>
    /// <param name="ChildsProperty">Property to return child node</param>
    /// <param name="Match">Predicate for matching</param>
    /// <returns>The instance of matched result, null if not found</returns>
    public static T DepthFirstSearch<T, TChilds>(this T node, Func<T, TChilds> ChildsProperty, Predicate<T> Match) 
        where T:class
    {
        if (!(ChildsProperty(node) is IEnumerable<T>))
            throw new ArgumentException("ChildsProperty must be IEnumerable<T>");

        Stack<T> stack = new Stack<T>();
        stack.Push(node);
        while (stack.Count > 0) {
            T thisNode = stack.Pop();
            #if DEBUG
            System.Diagnostics.Debug.WriteLine(thisNode.ToString());
            #endif
            if (Match(thisNode))
                return thisNode;
            if (ChildsProperty(thisNode) != null) {
                foreach (T child in (ChildsProperty(thisNode) as IEnumerable<T>).Reverse()) 
                    stack.Push(child);
            }
        }
        return null;
    }
0 голосов
/ 27 апреля 2011

Вам может понравиться это:

        public static bool DepthFirstSearch<T>(this IEnumerable<T> vertices, T rootVertex, T targetVertex, Func<T, IEnumerable<T>> getConnectedVertices, Func<T, T, bool> matchFunction = null)
    {
        if (getConnectedVertices == null)
        {
            throw new ArgumentNullException("getConnectedVertices");
        }
        if (matchFunction == null)
        {
            matchFunction = (t, u) => object.Equals(t, u);
        }
        var directlyConnectedVertices = getConnectedVertices(rootVertex);
        foreach (var vertex in directlyConnectedVertices)
        {
            if (matchFunction(vertex, targetVertex))
            {
                return true;
            }
            else if (vertices.DepthFirstSearch(vertex, targetVertex, getConnectedVertices, matchFunction))
            {
                return true;
            }
        }
        return false;
    }
0 голосов
/ 27 апреля 2011

Элементы будут извлечены из стека в обратном порядке, так же, как они помещаются в него:

stach.push () приводит к: 1 2 3 4 5

stack.pop () приводит к: 5 4 3 2 1 (так: справа налево)

...