使用广度遍历算法寻找两点之间的最短路径

 
1、问题的提出
       玩过魔兽争霸、星际争霸或者各种SLG游戏的朋友基本都会发现一个现象,就是游戏中的单位,不管你指定地图上的任何一点,单位都能寻找到一条最短的路径移至终点。
       在本例中,笔者将采用广度遍历的算法来展现一下如何寻找到一条最短路径到达终点。
 

2、问题的研究

       为了找到合适的算法,我们必须首先建模,以确定我们应该采取的解决办法。

   如图中所示,红点代表起点,绿点代表终点,在布满随机障碍的地图中,算法必须找到一条最短路径以到达终点。

       很多初接触这个问题的人都会习惯性地想到两点之间直线最短。因而会想构造一个算法,让单位尽可能往终点位置移动,如果在移动过程中遇到障碍再构造一套算法来绕过障碍,将得到的路线优化以得到最短路径。

       笔者曾在这种思路下苦思冥想了N久,最后认为以笔者目前的水平想要实现这种智能化的程序,机率微乎其微。于是笔者只好改变最初的思路,凑巧笔者在蔓延一词的启发中找到思路,即以起点为圆心,可以行到起点周围的四个可及点,判断每个可即点是否为终点,如果不是,则以四个可及点为圆心,遍历四个可及点周围的可及点是否为终点,以此类推。

3、问题的解决

       首先构造一个类,叫MapPoint,它用来表示地图上的一个点,并有自己的一系列属性。如下:

    public class  MapPoint
    
{
        
private PointType mPointType =
 PointType.Reachable;
        
/// <summary>
        
/// 点类型.
        
/// </summary>

        public PointType PointType
        
{
            
set

            
{
                mPointType 
=
 value;
            }

            
get
            
{
                
return
 mPointType;
            }

        }


        
private Point mLocation;
        
/// <summary>
        
/// 点所在位置.
        
/// </summary>

        public Point Location
        
{
            
set

            
{
                mLocation 
=
 value;
            }

            
get
            
{
                
return
 mLocation;
            }

        }


        
/// <summary>
        
/// 获取本点可到达的点集合.
        
/// </summary>

        public List<MapPoint> ReachablePoins
        
{
            
get

            
{
                
return
 GetReachablePoints(mOwnerMap.PointCollection);
            }

        }


        
private Map mOwnerMap;
        
/// <summary>
        
/// 本点所在的地图.
        
/// </summary>

        public Map OwnerMap
        
{
            
set

            
{
                mOwnerMap 
=
 value;
            }

            
get
            
{
                
return
 mOwnerMap;
            }

        }


        
private MapPoint mPrePoint;
        
/// <summary>
        
/// 设置或获取该点的前一点.在寻找到路径时使用该属性来确定路径.
        
/// </summary>

        public MapPoint PrePoint
        
{
            
set

            
{
                mPrePoint 
=
 value;
            }

            
get
            
{
                
return
 mPrePoint;
            }

        }


        
private int mR = 5;
        
/// <summary>
        
/// 设置或获取点的半径.
        
/// </summary>

        public int R
        
{
            
set

            
{
                mR 
=
 value;
            }

            
get
            
{
                
return
 mR;
            }

        }


        
/// <summary>
        
/// 获取本点所能到达的点.
        
/// </summary>

        
/// <param name="Map"></param>
        
/// <returns></returns>

        public List<MapPoint> GetReachablePoints(Dictionary<Point, MapPoint> Map)
        
{
            List
<MapPoint> rPoints = new List<MapPoint>
();
            
//检索X-1的坐标点

            Point searchPoint = new Point(this.Location.X - 1this.Location.Y);
            
if (Map.ContainsKey(searchPoint) && Map[searchPoint].PointType !=
 PointType.UnReachable)
            
{
                rPoints.Add(Map[searchPoint]);
            }

            
//检索X+1的坐标点
            searchPoint = new Point(this.Location.X + 1this.Location.Y);
            
if (Map.ContainsKey(searchPoint) && Map[searchPoint].PointType !=
 PointType.UnReachable)
            
{
                rPoints.Add(Map[searchPoint]);
            }

            
//检索Y-1的坐标点
            searchPoint = new Point(this.Location.X, this.Location.Y - 1);
            
if (Map.ContainsKey(searchPoint) && Map[searchPoint].PointType !=
 PointType.UnReachable)
            
{
                rPoints.Add(Map[searchPoint]);
            }

            
//检索Y+1坐标点
            searchPoint = new Point(this.Location.X, this.Location.Y + 1);
            
if (Map.ContainsKey(searchPoint) && Map[searchPoint].PointType !=
 PointType.UnReachable)
            
{
                rPoints.Add(Map[searchPoint]);
            }

            
return rPoints;
        }

    }

        以这些点所构成的即是地图,地图可以简单地理解为一组点的集合,我们用一个字典(Dictionary)来储存这些点。于是得到如下一个类:

     public   class  Map
    
{
        
private int xLenth = 0;
        
private int yLenth = 0;
        
private Dictionary<Point, MapPoint> map;
        Random r 
= new Random();

        
/// <summary>
        
/// 实例化一张地图.
        
/// </summary>
        
/// <param name="x">宽.</param>
        
/// <param name="y">高.</param>

        public Map(int x, int y)
        
{
            
if ((x < 0 || y < 0|| (x == 1 && y == 1))
            
{
                
throw new Exception("地图的长宽设置不合法!不允许小于或者同时为!");
            }

            xLenth 
= x;
            yLenth 
= y;
            map 
= new Dictionary<Point, MapPoint>();
        }


        
private int mUnreachableRandom = 0;
        
/// <summary>
        
/// 设置或获取地图的障碍率.
        
/// </summary>

        public int UnreachableRandom
        
{
            
set
            
{
                mUnreachableRandom 
= value;
            }

            
get
            
{
                
return mUnreachableRandom;
            }

        }


        
/// <summary>
        
/// 获取点集合.
        
/// </summary>
        
/// <returns></returns>

        public Dictionary<Point, MapPoint> PointCollection
        
{
            
get
            
{
                
return map;
            }

        }


        
private MapPoint mStart;
        
/// <summary>
        
/// 设置或获取起点.
        
/// </summary>

        public MapPoint Start
        
{
            
set
            
{
                
if (mStart != null)
                
{
                    mStart.PointType 
= PointType.Reachable;
                }

                mStart 
= value;
            }

            
get
            
{
                
return mStart;
            }

        }


        
private MapPoint mEnd;
        
/// <summary>
        
/// 设置或获取终点.
        
/// </summary>

        public MapPoint End
        
{
            
set
            
{
                
if (mEnd != null)
                
{
                    mEnd.PointType 
= PointType.Reachable;
                }

                mEnd 
= value;
            }

            
get
            
{
                
return mEnd;
            }

        }


        
private int mPointR = 5;
        
/// <summary>
        
/// 设置或获取地图中每点的半径.
        
/// </summary>

        public int PointR
        
{
            
set
            
{
                mPointR 
= value;
            }

            
get
            
{
                
return mPointR;
            }

        }


}

注意:在本类中,我们用一个字典来存储点集合,而且字典的关键字(KEY)我们用的是Point,这种使用方法看起来有点别忸,但是由于Point是一个结构体(struct),而且其Equals方法已被重写,所以可以任意实例化一个Point在字典中寻找具有同样XY值的Value,如果你使用的是一个类做为字典的KEY,则无法实现这一点。

       以下则是本例的核心算法,广度遍历算法,广度遍历可以理解为,以一组已知的集合为搜索起点,在这组集合的基础上寻找新的一组集合,并以新的一组集合为起点再搜索更新的集合,以此类推。

/// <summary>
        
/// 用来储存已遍历过的点.
        
/// </summary>

        List < MapPoint >  searched  =   new  List < MapPoint > ();
        
/// <summary>
        
/// 广度遍历.
        
/// </summary>
        
/// <param name="points">上一轮遍历得到的点集合.</param>
        
/// <returns>是否找到终点.</returns>

         private   bool  SearchEnd(List < MapPoint >  points)
        
{
            
//这是下一轮应该寻找的点集合.
            List<MapPoint> nextPoints = new List<MapPoint>();
            
foreach (MapPoint point in points)
            
{
                searched.Add(point);
                
if (point.ReachablePoins.Count > 0)
                
{
                    
foreach (MapPoint p in point.ReachablePoins)
                    
{
                        
if (searched.Contains(p))
                        
{
                            
continue;
                        }

                        
else
                        
{
                            
if (!nextPoints.Contains(p))
                                nextPoints.Add(p);
                            p.PrePoint 
= point;
                            
if (p.Equals(mEnd))
                            
{
                                
return true;
                            }

                        }

                    }

                }

            }

            
if (nextPoints.Count == 0)
            
{
                
return false;
            }

            
else
            
{
                
return SearchEnd(nextPoints);
            }

        }

至此,算法基本完成,将点展现在图上,就可以得到文章开始的搜索图,另外我们还可以画出起点到终点在寻找过程中的蔓延图。

如图,粉色表示红点在寻找绿点的过程中蔓延过的点,由于我们取的是第一个蔓延到绿点的线,因此可以认为该路线是最短路径。

4、方案的不足

    很明显,在此套算法中,最大的不足就是效率问题,消耗的时间随起点与终点之间距离的增加而递增,随障碍率的增加而减少。

5、结语

    这次的算法花了笔者起码5个小时的时间才想出来并将程序写好、整理完成。如需转载请注明出处,体谅一下笔者辛苦的劳动。

    附上DEMO及原码,喜欢这个问题的朋友可以一起探讨更优的解法。并希望大家提出改进的办法,批评指正。

http://download.csdn.net/source/397878

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值