基于C#可应用于Unity的Dijkstra算法解决最短路径问题

1,337 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

Dijkstra理论模型

作为大型RPG游戏寻路是一个很重要的环节,在A*出现之前叱咤风云的算法还是当之无愧的属于迪杰斯特拉算法,那么,什么是迪杰斯特拉算法呢?迪杰斯特拉算法是一位荷兰计算机大佬迪杰斯特拉1959年提出的一种思想,它是最典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。最大之特点是以起始点为中心向外层层扩展,一直到终点,也就是说它的轨迹是从内到外的螺旋形轨迹。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容的详细简介,当然,我不是计算机专业,我是电气工程及其自动化转行过来的,目前在职,IT方面是自学来的,所以说数据结构,图论以及运筹学都是自己业余时间看资料学来的。废话不多说了,我先贴个我干刚刚画的图大家看下。↓↓

image.png 这是一个无向图,啥意思?就是没有具体方向的图啊!看图↑,这上边有5个点7条边。首先,我们找到一个点,我们按照顺序从a点开始。

设:A点坐标是A(-,0),我们把其余与A点相关的点到A点的距离计算一下,根据路径上的权值计算出长度和图中的一样先记录数据,B(A,3)和D(A,7)。

好了现在我们可以确定B点到A的距离最短所以确定边AB,此时B的权重为3,然后从B出发,找出所有与B相连的点把各个路径上的权重与B相加。得到D(B,3+2)和C(B,3+4)。现在我们确定一下第二条边BD吧,D的权值为5,权值最小,∴第三个点应该是D点。

根据这个思路我们继续走,以后的点都是这么找,与D点相连的有C,E两个点,我们计算出来的长度是C(D,10)和E(E,5+4)。此时C的权重为10了,而C为7,

∴应该选择边BC而不是DC。现在还剩下最后一个点E,从C到E是(C,13),从D到E是(d,6)。

∴选择路径D-E,

∴最终路径是A-B-D-E。

下面附上我写的代码实现的Dijkstra算法的流程:↓↓

struct MGraph
{
    public int[,] edges;
    public int n, e;
}

定义结构体里边放一个二维数组和顶点变量n和变数变量e

        const int MAX_V = 100;
        const int INF = 32767;
        MGraph mg = new MGraph();

        /// <summary>
        /// U集合
        /// </summary>
        private List<int> uList = new List<int>();
        /// <summary>
        /// S集合
        /// </summary>
        private Sset sset = new Sset();

        public GraphClass()
        {
            mg.edges = new int[MAX_V, MAX_V];
        }

        /// <summary>
        /// 创建图的矩阵
        /// </summary>
        /// <param name="n">顶点的个数</param>
        /// <param name="e">边数</param>
        /// <param name="a">图的矩阵存储</param>
        public void CreateMGraph(int n, int e, int[,] a)
        {
            mg.n = n;
            mg.e = e;

            for (int i = 0; i < mg.n; i++)
            {
                uList.Add(i);
                for (int j = 0; j < mg.n; j++)
                {
                    mg.edges[i, j] = a[i, j];
                }
            }
        }

※创建S集合和U集合,这里有必要说明一下S集合和U集合,初始的时候S集合只包含远点,所以S = v,v = 0,U集合是包含除了v以外其他的顶点,U中顶点u距离为边上的权。然后从U集合中选取一个距离v最小的顶点n,把n加到S集合里,这时该选定的距离就是v到n的最短路径了。

继续,以n为最新考虑的中间点,修改U集合中各顶点的距离;若从原点v到顶点n经过n顶点的距离比原来不经过n顶点的距离要短,则要修改顶点n的距离值,修改之后的距离值的顶点n的距离加上边上的权。

之后,上述的步骤重复进行……因为这个S集合有点特殊,我把它封装了一个类,如下图↓↓

/// <summary>
/// S集合
/// </summary>
public class Sset
{
    public List<int> list;
    public Dictionary<string, int> dic;
    public Sset()
    {
        list = new List<int>();
        dic = new Dictionary<string, int>();
    }
}

S集合的构造函数中包含一个集合和一个链表。
下面写一个函数把矩阵图给打印出来↓↓!

/// <summary>
/// 输出图的矩阵
/// </summary>
/// <returns></returns>
public string DispMGraph()
{
    string str = string.Empty;
    for (int i = 0; i < mg.n; i++)
    {
        if (i == 0)
            str += "   A   B   C   D   E   F\r\n";
        for (int j = 0; j < mg.n; j++)
        {
            if (j == 0)
                str = (ZM)i + " ";
            str += string.Format("{0,-4}", mg.edges[i, j].ToString());
        }
        str += "\r\n";
    }
    return str;
}

注:ZM是一个枚举,里边包含了6个顶点A,B,C,D,E,F的编号依次是0,1,2,3,4,5。下面贴上Dijkstra核心算法Dijkstra函数↓↓

public void Dijkstra()
{
    sset.list.Add(0);
    sset.dic.Add("0", 0);
    sset.dic.Add("0-0", 0);
    int max = 100000;
    int uListPosition = 0;
    string path = string.Empty;
    while (uList.Count > 1)
    {
        foreach (KeyValuePair<string,int> item in sset.dic)
        {
            string[] temp = item.Key.Split('-');
            int last = Convert.ToInt32(temp[temp.Length - 1]);
            for (int i = 0; i < uList.Count; i++)
            {
                if (!temp.Contains(uList[i].ToString()))
                {
                    if (mg.edges[last, uList[i]] != -1 && mg.edges[last, uLi
                    {
                        max = mg.edges[last, uList[i]];
                        uListPosition = i;
                        path = item.Key;
                    }
                }
            }
        }
        sset.list.Add(uList[uListPosition]);
        sset.dic.Add(path + "-" + uList[uListPosition], max + sset.dic[path]
        uList.Remove(uList[uListPosition]);
        max = 100000;
    }
    foreach (KeyValuePair<string,int> item in sset.dic)
    {
        string[] temp = item.Key.Split('-');
        string consoleString = "";
        foreach (string itemStr in temp)
        {
            consoleString += (ZM)(Convert.ToInt32(itemStr)) + "-";
        }
        consoleString = consoleString.Remove(consoleString.LastIndexOf('-'))
        Console.WriteLine(consoleString + "距离:" + item.Value);
    }
}

遍历S集合,实现的思路是上述标“※”的部分。然后我们来到Main函数,写几个点的坐标,设顶点数是6,边数是9.将矩阵图和迪杰斯特拉路径打印出来~

class Program
{
    static void Main(string[] args)
    {
        int n = 6, en = 9;
        /// <summary>
        /// -1表示不可达
        /// </summary>
        int[,] a = new int[,]
        {
            {0,6,3,-1,-1,-1},
            {6,0,2,5,-1,-1},
            {3,2,0,3,4,-1},
            {-1,5,3,0,2,3},
            {-1,-1,4,2,0,5},
            {-1,-1,-1,3,5,0}
        };
        GraphClass graphClass = new GraphClass();
        graphClass.CreateMGraph(n, en, a);
        Console.WriteLine(graphClass.DispMGraph());
        graphClass.Dijkstra();
        Console.ReadKey();
    }
}

这样我们的流程就走完了,按下F5打开控制台调试一下↓↓

image.png

下面附上我的Dijkstra算法的源码百度云网盘地址:↓↓链接:pan.baidu.com/s/1nvcJwu9 密码:emt5)

这就是迪杰斯特拉算法C#的编程思想,前提大家要有一些数学中解析几何的基本知识才能破解这个难题,风暴洋PABLO.Y祝大家学习愉快。