@Michail7

Поиск всех маршрутов графа C#?

Есть граф описанный матрицей

0 1 1 1 0 0 0
1 0 0 0 0 0 0
1 0 0 1 0 0 1
1 0 1 0 0 1 0
0 0 0 0 0 1 1
0 0 0 1 1 0 1
0 0 1 0 1 1 0

Необходимо из вершины v построить все маршруты до вершины x

Вот, что я написал

private void dfs(int[][] a, ref bool[] visited, int n, int v, int x, ref int cnt, ref List<List<int>> p, ref List<int> r)
            {
                if (v == x)
                {
                    cnt++;
                    r.Add(x + 1);
                    p.Add(r);
                    r = new List<int>();
                    return;
                }
                visited[v] = true;
                r.Add(v + 1);
                for (int i = 0; i < n; i++)
                {
                    if (a[v][i] == 1 && !visited[i])
                    {
                        dfs(a, ref visited, n, i, x, ref cnt, ref p, ref r);
                    }
                }
                visited[v] = false;
                r.Remove(v + 1);
            }

тут a- матрица графа, visited - массив посещенных вершин, v- начальная вершина, x - конечная вершина, cnt - количество маршрутов, p - список маршрутов, r - текущий маршрут

Но он некорректно работает, подскажите как это исправить.
  • Вопрос задан
  • 229 просмотров
Решения вопроса 1
@Michail7 Автор вопроса
Вот изменённый алгоритм, он отрабатывает корректно.

private void findRoute(int[][] a, int v, int dest, int n, bool[] visited, string route)
    {
        if (v == dest)
        {
            richTextBox1.Text += route + "\n";
        }
        else
        {
            visited[v] = true;
            for (int i = 0; i < n; i++)
            {
                if (a[v][i] == 1 && !visited[i])
                {
                    findRoute(a, i, dest, n, visited, route + (i+1) + " ");
                }
            }
            visited[v] = false;
        }
    }
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
@Arlekcangp
Разработчик, Лид, Архитектор ПО
По-моему у вас рекурсия организована не совсем верно. Дело в том, что r передаётся по ссылке во все ветви поиска. И если одна из этих ветвей завершается успешно, r обнуляется, тем самым сбрасывая конкурирующие пути. Кроме того и в цикле поиска r так же должна всегда содержать тоже самое (т е путь до текущей вершины v. Но в вашем коде это не так, потому что r будет меняться внутри рекурсивного вызова.
Я не уверен, что всё остальное правильно, но я бы поменял:
dfs(a, ref visited, n, i, x, ref cnt, ref p, ref r);

на
dfs(a, ref visited, n, i, x, ref cnt, ref p,  r.ToArray());

И соответственно ref убрал бы из определения dfs. Так при переходе по всем вершинам путям в цикле будет для каждого пути создана своя копия r. Так же в этом случае не нужен r.Remove(v + 1); и r = new List();
Вообще передача большого количества аргументов по ссылке не очень хорошее решение с архитектурной точки зрения. Вам стоит подумать как организовать код так, что бы рекурсивная функция была "чистой" - т е её вызов с одними и теми же параметрами всегда приводил бы к одному результату. Умение писать в таком стиле сильно уменьшает вероятность ошибки.

Update:
По просьбе Michail7 привожу свой вариант решения:
class Program
    {

        public  struct Path {
            public  List<int> Vertexes { readonly get;  private set; } 
            
            public Path(int vertex) {
                this.Vertexes = new List<int>();
                this.Vertexes.Add(vertex);
            }

            public Path(List<int> vertexes, int vertex) {
                if (vertexes != null) {
                    this.Vertexes = new List<int>(vertexes);
                }
                else {
                    this.Vertexes = new List<int>();
                }
                this.Vertexes.Add(vertex);
            }

            public Path Add(int vertex) {
                Path p = new Path(this.Vertexes, vertex);
                return p;
            }

            public bool hasVertex(int vertex) {
                return Vertexes.Contains(vertex);
            }

            public int LastVertex() {
                return Vertexes.Last();
            }
        }

        public static List<Path> RunDfs(in  int[,] graph, int n, in Path currentPath, int targetVertex) {
            int currentVertex = currentPath.LastVertex();
            if (currentVertex == targetVertex) {
                return new List<Path> { currentPath };
            }
            else {
                List<Path> result = new List<Path>();
                for (int i = 0; i < n; i++) {
                    if (graph[currentVertex,i] == 1 && ! currentPath.hasVertex(i) ) {
                        List<Path> newPaths = RunDfs(graph, n, currentPath.Add(i), targetVertex );
                        result.AddRange(newPaths);
                    }
                }
                return result;
            }
        }

        static void Main(string[] args) {
            int[,] graph =  { {0,1,1,0},{0,0,0,1},{0,1,0,0}, {0,0,0,0}};
            Path curPath = new Path().Add(0);
            List<Path> paths = RunDfs(graph, 4,  curPath, 3);
            Console.WriteLine("Hello World!");
        }

Его наверняка можно улучшить. И не факт, что он по скорости выиграет. Но. В нём сведено к миниму количество параметров и метод RunDfs является "чистым" в том понятии, что для одинакового входа он всегда выдаёт тот же выход и не имеет сайд-эффектов в виде записи в передаваемые структуры. Единственное, что я не стал оптимизировать - это передачу графа в виде двумерного массива. Очевидно для настоящих больших графов так оставлять нельзя, т к есть вероятность, что компиллятор всё же решит его копировать по значению, несмотря на то, что запись внутри RunDfs в него не происходит. (Это наверное можно проверить, делая бенчмарки по времени на графе достаточного размера и сравнив версию, где граф передаётся через ref) Но правильнее и проще для графа также ввести какую то структуру или даже класс, который будет передаваться по ссылке, но при этом не будет давать себя менять.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы