1. 程式人生 > >A*演算法的C#實現

A*演算法的C#實現

目錄

1,概述

5,測試

1,概述

        本文的主要內容是講述A *尋路演算法的基本原理,實現步驟以及對應的C#程式碼,適合讀者用於學習A *演算法或

使用此程式碼提供的介面完成遊戲中的尋路功能。

2,A *演算法的基本原理

        A *演算法是一種經典的啟發式演算法,演算法的核心是將節點n到目標點的代價定義為f(n)= g(n)+ h(n),其中 g(n)表示出發點到節點ñ的距離,h(n)是一種啟發式函式,表示節點ñ到目標點的評估代價,通常為了簡化我們採用曼哈頓距離來模擬。知道 f(n)如何計算後,A *演算法還有兩個重要的集合——open列表和closed列表,

open列表用於儲存當前可以選擇移動的所有節點,closed列表用於儲存走過的所有節點。首先,我們將出發點加入open列表中,然後每次從open列表中選擇f(n)最小的節點,然後將該節點從open列表中移除,並加入到closed列表中,並將該節點周圍沒有走過的所有可達節點加入或更新到open列表中,重複選擇節點直到目標點在closed列表中為止,再通過儲存節點的parent回溯,這樣就可以得到兩個點之間的一條最短路徑。

3,A *演算法的實現步驟

①將出發點加入到open列表中。

②從open列表中選擇 f(n)最小的節點k,將節點k從open列表中移除,並將其加入到closed列表中。

③對於節點ķ周圍距離為1的每個可達節點t,執行以下操作:

        a.如果t在closed列表中,丟棄這個節點;

        b.如果t不在open列表中,將其加入open列表中;

        c.如果t在open列表中,計算其f(n)並和open列表中該節點的f(n)的對比,如果它的f(n)更小,則更新open列表中該節點的資訊。

④重複②③直到目標點在closed列表中(表明求得最短路徑)或open列表為空(表明終點不可達)。

4,A *演算法的C#實現

檔名:

AStar.cs

演算法執行介面:

AStar.Instance.Execute(int [,] map,int srcX,int srcY,int distX,int distY,int reachableVal = 0,bool allowDiagonal = false);

輸出路徑介面:

AStar.Instance.DisplayPath(ANode aNode);

完整程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

/******************************************************************************** 
** auth:    FengLinyi
** date:    2018/09/01
** desc:    A*演算法的實現
** Ver.:     V1.0.0
*********************************************************************************/

namespace DeepCSharp
{
    class AStar
    {
        /// <summary>
        /// 二維座標點
        /// </summary>
        public struct Point
        {
            public int x, y;
            public Point(int _x, int _y)
            {
                x = _x;
                y = _y;
            }
        }
        /// <summary>
        /// A*的每個節點
        /// </summary>
        public class ANode
        {
            public Point point;
            public ANode parent;
            public int fn, gn, hn;
        }
        private AStar() { }
        public static AStar Instance { get; } = new AStar();
        private int[,] map = null;
        private Dictionary<Point, ANode> openList = null;
        private HashSet<Point> closedList = null;
        private Point dist;
        private int reachableVal;

        /// <summary>
        /// 執行演算法
        /// </summary>
        /// <param name="map">二維網格地圖,邊緣需要用不可達的值填充</param>
        /// <param name="srcX">當前點X座標</param>
        /// <param name="srcY">當前點Y座標</param>
        /// <param name="distX">目標點X座標</param>
        /// <param name="distY">目標點Y座標</param>
        public ANode Execute(int[,] map, int srcX, int srcY, int distX, int distY, int reachableVal = 0, bool allowDiagonal = false)
        {
            openList = new Dictionary<Point, ANode>();
            closedList = new HashSet<Point>();
            this.map = map;
            this.dist = new Point(distX, distY);
            this.reachableVal = reachableVal;
            //將初始節點加入到open列表中
            ANode aNode = new ANode();
            aNode.point = new Point(srcX, srcY);
            aNode.parent = null;
            aNode.gn = 0;
            aNode.hn = ManHattan(aNode.point, dist);
            aNode.fn = aNode.gn + aNode.hn;
            openList.Add(aNode.point, aNode);

            while (openList.Count > 0)
            {
                //從open列表中找到f(n)最小的結點
                ANode minFn = FindMinFn(openList);
                Point point = minFn.point;
                //判斷是否到達終點
                if (point.x == dist.x && point.y == dist.y) return minFn;
                //去除minFn,加入到closed列表中
                openList.Remove(minFn.point);
                closedList.Add(minFn.point);
                //將minFn周圍的節點加入到open列表中
                AddToOpenList(new Point(point.x - 1, point.y), minFn); //左
                AddToOpenList(new Point(point.x + 1, point.y), minFn); //右
                AddToOpenList(new Point(point.x, point.y - 1), minFn); //上
                AddToOpenList(new Point(point.x, point.y + 1), minFn); //下
                if(allowDiagonal)
                {
                    AddToOpenList(new Point(point.x - 1, point.y - 1), minFn); //左上
                    AddToOpenList(new Point(point.x + 1, point.y - 1), minFn); //右上
                    AddToOpenList(new Point(point.x - 1, point.y + 1), minFn); //左下
                    AddToOpenList(new Point(point.x + 1, point.y + 1), minFn); //右下
                }
            }
            return null;
        }

        /// <summary>
        /// 輸出最短路徑
        /// </summary>
        /// <param name="aNode"></param>
        public void DisplayPath(ANode aNode)
        {
            while(aNode != null)
            {
                Console.WriteLine(aNode.point.x + "," + aNode.point.y);
                aNode = aNode.parent;
            }
        }

        /// <summary>
        /// 判斷節點是否可達,可達則將節點加入到open列表中
        /// </summary>
        /// <param name="a"></param>
        /// <param name="parent"></param>
        private void AddToOpenList(Point point, ANode parent)
        {
            if(IsReachable(point) && !closedList.Contains(point))
            {
                ANode aNode = new ANode();
                aNode.point = point;
                aNode.parent = parent;
                aNode.gn = parent.gn + 1;
                aNode.hn = ManHattan(point, dist);
                aNode.fn = aNode.gn + aNode.hn;
                if (openList.ContainsKey(aNode.point))
                {
                    if (aNode.fn < openList[aNode.point].fn)
                    {
                        openList[aNode.point] = aNode;
                    }
                }
                else
                    openList.Add(aNode.point, aNode);
            }
        }

        /// <summary>
        /// 判定該點是否可達
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        private bool IsReachable(Point a)
        {
            return map[a.y, a.x] == this.reachableVal;
        }

        /// <summary>
        /// 計算兩個點之間的曼哈頓距離
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private int ManHattan(Point a, Point b)
        {
            return Math.Abs(a.x - b.x) + Math.Abs(a.y - b.y);
        }

        /// <summary>
        /// 從open列表中獲取最小f(n)的節點
        /// </summary>
        /// <param name="aNodes"></param>
        /// <returns></returns>
        private ANode FindMinFn(Dictionary<Point, ANode> aNodes)
        {
            ANode minANode = null;
            foreach(var e in aNodes)
            {
                if(minANode == null || e.Value.fn < minANode.fn)
                {
                    minANode = e.Value;
                }
            }
            return minANode;
        }
    }
}

5,測試

主函式中的程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DeepCSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            int[,] map =
            {
                {1,1,1,1,1,1,1,1 },
                {1,0,0,0,1,1,1,1 },
                {1,1,1,0,1,1,1,1 },
                {1,1,0,0,1,0,0,1 },
                {1,1,0,0,0,0,0,1 },
                {1,1,1,1,1,1,1,1 },
            };
            var node = AStar.Instance.Execute(map, 1, 1, 6, 4);
            AStar.Instance.DisplayPath(node);
        }
    }
}

測試結果: