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);
            }

 

posted @ 2020-07-04 18:25  热敷哥  阅读(878)  评论(0编辑  收藏  举报