• C# 广度优先搜索


     

    广度优先搜索是一种用于图的查找算法,它主要解决两个问题:

    1.从节点S到节点E有路径吗?

    2.从节点S到节点E的所有路线中,哪条最短?

    广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系.

    所谓一度关系:我的朋友和我就是一度关系.

    所谓二度关系:我的朋友的朋友和我就是二度关系.

    以此类推.

    曾经不知道在哪里看到过一句话:

    解决问题,先确定数据结构,数据结构确定好了,算法自然而然就出来了.不过我觉得我离这个境界还有点远....

    那么这个算法应该用哪种数据结构来存储数据呢?

    因为一个节点可以指向多个节点,当然,也可以不指向任何节点.所以我们可以用散列表: Dictionary<T,List<T>> (也许还有其他方式,但是小弟确实不懂,只懂这个)

    由于多个节点可以指向同一个节点,自己也可以指向自己,所以在搜索的过程中,对于检查过的节点,我们不能再去检查,否则可能会导致无限循环,因此我们需要一个数据结构来保存搜索过的节点,这个用 List<T> 或者 HashSet<T> 都可以.

    前面说到了,该算法搜索的时候是从内到外,一层一层搜索,最里面一层搜索完了,递增一层继续搜索,逐渐向外延伸.

    那么这里肯定需要一种数据结构来存储需要检查的节点.

    同时,由于是从内到外,层层递增搜索,所有这里就有个先后顺序的问题了.那就是必须要把第一层的节点搜索完,才能搜索第2层,第2层的节点搜索完,才能搜索第3层.像这种有先后顺序要求的数据结构,必须队列:Queue<T>,我们将需要搜索的节点放到队列中,比如:

    起点S的第1层关系中有2个节点:A和B,我们先把这两个节点插入队列.那么这个插入有顺序要求吗?没有!同一层的节点插入到队列的顺序不重要,因为该算法计算的是最短距离,不是最快距离.

    假设顺序是A,B,我们先检查A,如果A不是我们要找的终点(假设是E),那么我们就把A指向的所有节点插入到队列,插入到队列后,它们是排在B的后面,所以只有等第1层的B检查完了,才会检查它们.当B检查完了,也不是我们要找的终点,我们再把B指向的所有节点插入到队列,这也就实现了1层1层的递增检查.

    通过上面的方法,我们可以解决开篇提到的第1个问题:从S到E是否有路径.但是无法解决第2个问题:最短路径是哪条.

    要解决这个问题,在搜索的过程中就必须保存当前搜索的节点是从哪个节点过来的.因为前面也提到了,在图这种数据结构中,多个节点是可以指向同一个节点的.并且一个节点也是可以指向多个节点的,所以我们必须清楚的知道,当前搜索的节点是从哪个节点(哪条线路)过来的.

    打个不太恰当的比方:

    5+5=10

    但是10!=5+5 ,10还可以==1+9,==2+8,==3+7 ......

    所以,前面提到的,创建一个队列来保存需要检查的节点是不行的,我们还需要保存节点的父节点,因为我们需要知道我们是怎么来的!

    因此,我封装了一下当前节点类型,提供了一个属性来保存它的父亲.

    (这个设计可能不太好,额外空间可能耗得比较多,小弟暂时没想到什么好的办法)

    代码如下:

        public class Route<T>
        {
            /// <summary>
            /// 终点相对于起点的维度
            /// </summary>
            public int Dimension { get; }
    
            /// <summary>
            /// 完整路线
            /// </summary>
            public string FullRoute { get; }
    
            public Route(Stack<T> stack)
            {
                FullRoute = string.Join(",", stack);
                Dimension = stack.Count - 1;
            }
        }
        public class RouteNode<T>
        {
            public T Value { get; }
            public RouteNode<T> Parent { get; set; }
    
            public RouteNode(T value)
            {
                Value = value;
            }
    
            public Route<T> Route
            {
                get
                {
                    var stack = new Stack<T>();
                    stack.Push(this.Value);
                    var parent = Parent;
                    while (parent != null)
                    {
                        stack.Push(parent.Value);
                        parent = parent.Parent;
                    }
                    Route<T> route = new Route<T>(stack);
                    return route;
                }
            }
        }
        public class MyGraph<T> where T : IComparable<T>
        {
            //节点集合
            private readonly Dictionary<T, IList<T>> _nodes;
    
            public MyGraph() : this(new Dictionary<T, IList<T>>())
            {
    
            }
    
            public MyGraph(Dictionary<T, IList<T>> nodes)
            {
                _nodes = nodes;
            }
    
            public void Add(T key, IList<T> value)
            {
                _nodes.Add(key, value);
            }
    
            /// <summary>
            /// 判断是否有从 start 到 end 的路径
            /// </summary>
            /// <param name="start">开始点</param>
            /// <param name="end">结束点</param>
            /// <param name="route">路线</param>
            /// <returns></returns>
            public bool TryFindMinRoute(T start, T end, out Route<T> route)
            {
                route = null;
                if (_nodes.TryGetValue(start, out var nodes) == false)
                {
                    throw new Exception("not find the element:" + start);
                }
                if (nodes == null || nodes.Count == 0)
                {
                    return false;
                }
                //已搜索元素的集合
                var searched = new HashSet<T>();
                //将要搜索的节点队列
                var searching = new Queue<RouteNode<T>>();
                foreach (var item in nodes)
                {
                    searching.Enqueue(new RouteNode<T>(item)
                    {
                        Parent = new RouteNode<T>(start),
                    });
                }
    
                while (searching.Count > 0)
                {
                    RouteNode<T> node = searching.Dequeue();
    
                    //判断当前元素是否搜索过,避免无限循环.
                    if (searched.Contains(node.Value))
                    {
                        continue;
                    }
    
                    if (node.Value.CompareTo(end) == 0)
                    {
                        route = node.Route;
                        return true;
                    }
    
                    searched.Add(node.Value);
    
                    if (_nodes.TryGetValue(node.Value, out var forwardNodes) == false || forwardNodes == null || forwardNodes.Count == 0)
                    {
                        //说明 当前元素 没有指向任何元素
                        continue;
                    }
    
                    foreach (var item in forwardNodes.Where(item => searched.Contains(item) == false))
                    {
                        searching.Enqueue(new RouteNode<T>(item)
                        {
                            Parent = node
                        });
                    }
                }
                return false;
            }
        }

    Test:

                MyGraph<string> dic = new MyGraph<string>();
                dic.Add("start", new List<string> { "bob", "alice", "claire" });
                dic.Add("bob", new List<string> { "anuj", "dandan1" });
                dic.Add("alice", new List<string> { "peggy" });
                dic.Add("claire", new List<string> { "thom", "jonny" });
                dic.Add("anuj", new List<string>() { "wahaha", "dandan2" });
                dic.Add("dandan2", new List<string>() { "wahaha", "claire" });
                dic.Add("peggy", new List<string>() { "gongwei" });
                dic.Add("gongwei", new List<string>() { "jonny" });
                dic.Add("thom", new List<string>());
                dic.Add("jonny", new List<string> { "refuge" });
                dic.Add("wjire", new List<string>());
                dic.Add("refuge", new List<string>() { "dandan", "bob" });
                var r = dic.TryFindMinRoute("alice", "claire", out var route);
                if (r)
                {
                    Console.WriteLine($"在第{route.Dimension}层找到:" + route.FullRoute);
                }
                else
                {
                    Console.WriteLine(r);
                }
  • 相关阅读:
    C库中与时间相关的函数
    读书笔记之: 程序员的自我修养——链接,装载与库(第1,2部分)
    STL中常用的一些算法
    C++ STL中的迭代器技术
    程序员面试宝典2(数据结构与算法)
    C/C++程序员面试宝典2
    读书笔记之: 数据库系统概论(第4版)
    C库中重要字符串函数的简要分析及实例
    程序员求职成功路(3)
    C库中对函数的可变参数的支持
  • 原文地址:https://www.cnblogs.com/refuge/p/13236025.html
Copyright © 2020-2023  润新知