经典算法题每日演练——第十七题 Dijkstra算法

原文:经典算法题每日演练——第十七题 Dijkstra算法

或许在生活中,经常会碰到针对某一个问题,在众多的限制条件下,如何去寻找一个最优解?可能大家想到了很多诸如“线性规划”,“动态规划”

这些经典策略,当然有的问题我们可以用贪心来寻求整体最优解,在图论中一个典型的贪心法求最优解的例子就莫过于“最短路径”的问题。

一:概序

从下图中我要寻找V0到V3的最短路径,你会发现通往他们的两点路径有很多:V0->V4->V3,V0->V1->V3,当然你会认为前者是你要找的最短

路径,那如果说图的顶点非常多,你还会这么轻易的找到吗?下面我们就要将刚才我们那点贪心的思维系统的整理下。

二:构建

如果大家已经了解Prim算法,那么Dijkstra算法只是在它的上面延伸了下,其实也是很简单的。

1.边节点

这里有点不一样的地方就是我在边上面定义一个vertexs来记录贪心搜索到某一个节点时曾经走过的节点,比如从V0贪心搜索到V3时,我们V3

的vertexs可能存放着V0,V4,V3这些曾今走过的节点,或许最后这三个节点就是我们要寻找的最短路径。

 1 #region 边的信息
 2         /// <summary>
 3         /// 边的信息
 4         /// </summary>
 5         public class Edge
 6         {
 7             //开始边
 8             public int startEdge;
 9
10             //结束边
11             public int endEdge;
12
13             //权重
14             public int weight;
15
16             //是否使用
17             public bool isUse;
18
19             //累计顶点
20             public HashSet<int> vertexs = new HashSet<int>();
21         }
22         #endregion

2.Dijkstra算法

首先我们分析下Dijkstra算法的步骤:

有集合M={V0,V1,V2,V3,V4}这样5个元素,我们用

TempVertex表示该顶点是否使用。

Weight表示该Path的权重(默认都为MaxValue)。

Path表示该顶点的总权重。

①. 从集合M中挑选顶点V0为起始点。给V0的所有邻接点赋值,要赋值的前提是要赋值的weight要小于原始的weight,并且排除已经访问过

的顶点,然后挑选当前最小的weight作为下一次贪心搜索的起点,就这样V0V1为挑选为最短路径,如图2。

②. 我们继续从V1这个顶点开始给邻接点以同样的方式赋值,最后我们发现V0V4为最短路径。也就是图3。

。。。

③. 最后所有顶点的最短路径就这样求出来了 。

 1 #region Dijkstra算法
 2         /// <summary>
 3         /// Dijkstra算法
 4         /// </summary>
 5         public Dictionary<int, Edge> Dijkstra()
 6         {
 7             //收集顶点的相邻边
 8             Dictionary<int, Edge> dic_edges = new Dictionary<int, Edge>();
 9
10             //weight=MaxValue:标识没有边
11             for (int i = 0; i < graph.vertexsNum; i++)
12             {
13                 //起始边
14                 var startEdge = i;
15
16                 dic_edges.Add(startEdge, new Edge() { weight = int.MaxValue });
17             }
18
19             //取第一个顶点
20             var start = 0;
21
22             for (int i = 0; i < graph.vertexsNum; i++)
23             {
24                 //标记该顶点已经使用过
25                 dic_edges[start].isUse = true;
26
27                 for (int j = 1; j < graph.vertexsNum; j++)
28                 {
29                     var end = j;
30
31                     //取到相邻边的权重
32                     var weight = graph.edges[start, end];
33
34                     //赋较小的权重
35                     if (weight < dic_edges[end].weight)
36                     {
37                         //与上一个顶点的权值累加
38                         var totalweight = dic_edges[start].weight == int.MaxValue ? weight : dic_edges[start].weight + weight;
39
40                         if (totalweight < dic_edges[end].weight)
41                         {
42                             //将该顶点的相邻边加入到集合中
43                             dic_edges[end] = new Edge()
44                             {
45                                 startEdge = start,
46                                 endEdge = end,
47                                 weight = totalweight
48                             };
49
50                             //将上一个边的节点的vertex累加
51                             dic_edges[end].vertexs = new HashSet<int>(dic_edges[start].vertexs);
52
53                             dic_edges[end].vertexs.Add(start);
54                             dic_edges[end].vertexs.Add(end);
55                         }
56                     }
57                 }
58
59                 var min = int.MaxValue;
60
61                 //下一个进行比较的顶点
62                 int minkey = 0;
63
64                 //取start邻接边中的最小值
65                 foreach (var key in dic_edges.Keys)
66                 {
67                     //取当前 最小的 key(使用过的除外)
68                     if (min > dic_edges[key].weight && !dic_edges[key].isUse)
69                     {
70                         min = dic_edges[key].weight;
71                         minkey = key;
72                     }
73                 }
74
75                 //从邻接边的顶点再开始找
76                 start = minkey;
77             }
78
79             return dic_edges;
80         }
81         #endregion

总的代码:复杂度很烂O(N2)。。。

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

namespace ConsoleApplication2
{
    public class Program
    {
        public static void Main()
        {
            Dictionary<int, string> dic = new Dictionary<int, string>();

            MatrixGraph graph = new MatrixGraph();

            graph.Build();

            var result = graph.Dijkstra();

            Console.WriteLine("各节点的最短路径为:");

            foreach (var key in result.Keys)
            {
                Console.WriteLine("{0}", string.Join("->", result[key].vertexs));
            }

            Console.Read();
        }
    }

    #region 定义矩阵节点
    /// <summary>
    /// 定义矩阵节点
    /// </summary>
    public class MatrixGraph
    {
        Graph graph = new Graph();

        public class Graph
        {
            /// <summary>
            /// 顶点信息
            /// </summary>
            public int[] vertexs;

            /// <summary>
            /// 边的条数
            /// </summary>
            public int[,] edges;

            /// <summary>
            /// 顶点个数
            /// </summary>
            public int vertexsNum;

            /// <summary>
            /// 边的个数
            /// </summary>
            public int edgesNum;
        }

        #region 矩阵的构建
        /// <summary>
        /// 矩阵的构建
        /// </summary>
        public void Build()
        {
            //顶点数
            graph.vertexsNum = 5;

            //边数
            graph.edgesNum = 6;

            graph.vertexs = new int[graph.vertexsNum];

            graph.edges = new int[graph.vertexsNum, graph.vertexsNum];

            //构建二维数组
            for (int i = 0; i < graph.vertexsNum; i++)
            {
                //顶点
                graph.vertexs[i] = i;

                for (int j = 0; j < graph.vertexsNum; j++)
                {
                    graph.edges[i, j] = int.MaxValue;
                }
            }

            //定义 6 条边
            graph.edges[0, 1] = graph.edges[1, 0] = 2;
            graph.edges[0, 2] = graph.edges[2, 0] = 5;
            graph.edges[0, 4] = graph.edges[4, 0] = 3;
            graph.edges[1, 3] = graph.edges[3, 1] = 4;
            graph.edges[2, 4] = graph.edges[4, 2] = 5;
            graph.edges[3, 4] = graph.edges[4, 3] = 2;

        }
        #endregion

        #region 边的信息
        /// <summary>
        /// 边的信息
        /// </summary>
        public class Edge
        {
            //开始边
            public int startEdge;

            //结束边
            public int endEdge;

            //权重
            public int weight;

            //是否使用
            public bool isUse;

            //累计顶点
            public HashSet<int> vertexs = new HashSet<int>();
        }
        #endregion

        #region Dijkstra算法
        /// <summary>
        /// Dijkstra算法
        /// </summary>
        public Dictionary<int, Edge> Dijkstra()
        {
            //收集顶点的相邻边
            Dictionary<int, Edge> dic_edges = new Dictionary<int, Edge>();

            //weight=MaxValue:标识没有边
            for (int i = 0; i < graph.vertexsNum; i++)
            {
                //起始边
                var startEdge = i;

                dic_edges.Add(startEdge, new Edge() { weight = int.MaxValue });
            }

            //取第一个顶点
            var start = 0;

            for (int i = 0; i < graph.vertexsNum; i++)
            {
                //标记该顶点已经使用过
                dic_edges[start].isUse = true;

                for (int j = 1; j < graph.vertexsNum; j++)
                {
                    var end = j;

                    //取到相邻边的权重
                    var weight = graph.edges[start, end];

                    //赋较小的权重
                    if (weight < dic_edges[end].weight)
                    {
                        //与上一个顶点的权值累加
                        var totalweight = dic_edges[start].weight == int.MaxValue ? weight : dic_edges[start].weight + weight;

                        if (totalweight < dic_edges[end].weight)
                        {
                            //将该顶点的相邻边加入到集合中
                            dic_edges[end] = new Edge()
                            {
                                startEdge = start,
                                endEdge = end,
                                weight = totalweight
                            };

                            //将上一个边的节点的vertex累加
                            dic_edges[end].vertexs = new HashSet<int>(dic_edges[start].vertexs);

                            dic_edges[end].vertexs.Add(start);
                            dic_edges[end].vertexs.Add(end);
                        }
                    }
                }

                var min = int.MaxValue;

                //下一个进行比较的顶点
                int minkey = 0;

                //取start邻接边中的最小值
                foreach (var key in dic_edges.Keys)
                {
                    //取当前 最小的 key(使用过的除外)
                    if (min > dic_edges[key].weight && !dic_edges[key].isUse)
                    {
                        min = dic_edges[key].weight;
                        minkey = key;
                    }
                }

                //从邻接边的顶点再开始找
                start = minkey;
            }

            return dic_edges;
        }
        #endregion
    }
    #endregion
}

  

时间: 2024-08-05 14:32:14

经典算法题每日演练——第十七题 Dijkstra算法的相关文章

经典算法题每日演练——第三题 猴子吃桃

原文:经典算法题每日演练--第三题 猴子吃桃 猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾就多吃了一个.第二天早上又将剩下的桃子吃了一半,还是不过瘾又多 吃了一个.以后每天都吃前一天剩下的一半再加一个.到第10天刚好剩一个.问猴子第一天摘了多少个桃子? 分析: 这是一套非常经典的算法题,这个题目体现了算法思想中的递推思想,递归有两种形式,顺推和逆推,针对递推,只要 我们找到递推公式,问题就迎刃而解了. 令S10=1,容易看出 S9=2(S10+1), 简化一下 S9=2S10+2 S8=2S

经典算法题每日演练——第二十题 三元组

原文:经典算法题每日演练--第二十题 三元组 我们知道矩阵是一个非常强大的数据结构,在动态规划以及各种图论算法上都有广泛的应用,当然矩阵有着不足的地方就是空间和时间 复杂度都维持在N2上,比如1w个数字建立一个矩阵,在内存中会占用1w*1w=1亿的类型空间,这时就会遇到outofmemory...那么面 临的一个问题就是如何来压缩矩阵,当然压缩的方式有很多种,这里就介绍一个顺序表的压缩方式:三元组. 一:三元组 有时候我们的矩阵中只有零星的一些非零元素,其余的都是零元素,那么我们称之为稀疏矩阵,

经典算法题每日演练——第七题 KMP算法

原文:经典算法题每日演练--第七题 KMP算法 在大学的时候,应该在数据结构里面都看过kmp算法吧,不知道有多少老师对该算法是一笔带过的,至少我们以前是的, 确实kmp算法还是有点饶人的,如果说红黑树是变态级的,那么kmp算法比红黑树还要变态,很抱歉,每次打kmp的时候,输 入法总是提示“看毛片”三个字,嘿嘿,就叫“看毛片算法”吧. 一:BF算法 如果让你写字符串的模式匹配,你可能会很快的写出朴素的bf算法,至少问题是解决了,我想大家很清楚的知道它的时间复 杂度为O(MN),原因很简单,主串和模

经典算法题每日演练——第二十一题 十字链表

原文:经典算法题每日演练--第二十一题 十字链表 上一篇我们看了矩阵的顺序存储,这篇我们再看看一种链式存储方法“十字链表”,当然目的都是一样,压缩空间. 一:概念 既然要用链表节点来模拟矩阵中的非零元素,肯定需要如下5个元素(row,col,val,down,right),其中: row:矩阵中的行. col:矩阵中的列. val:矩阵中的值. right:指向右侧的一个非零元素. down:指向下侧的一个非零元素. 现在我们知道单个节点该如何表示了,那么矩阵中同行的非零元素的表示不就是一个单链

经典算法题每日演练——第六题 协同推荐SlopeOne 算法

原文:经典算法题每日演练--第六题 协同推荐SlopeOne 算法 相信大家对如下的Category都很熟悉,很多网站都有类似如下的功能,“商品推荐”,"猜你喜欢“,在实体店中我们有导购来为我们服务,在网络上 我们需要同样的一种替代物,如果简简单单的在数据库里面去捞,去比较,几乎是完成不了的,这时我们就需要一种协同推荐算法,来高效的推荐浏览者喜 欢的商品. 一:概念 SlopeOne的思想很简单,就是用均值化的思想来掩盖个体的打分差异,举个例子说明一下: 在这个图中,系统该如何计算“王五“对”电

经典算法题每日演练——第十三题 赫夫曼树

原文:经典算法题每日演练--第十三题 赫夫曼树 赫夫曼树又称最优二叉树,也就是带权路径最短的树,对于赫夫曼树,我想大家对它是非常的熟悉,也知道它的应用场景, 但是有没有自己亲手写过,这个我就不清楚了,不管以前写没写,这一篇我们来玩一把. 一:概念 赫夫曼树里面有几个概念,也是非常简单的,先来看下面的图: 1. 基础概念 <1>  节点的权: 节点中红色部分就是权,在实际应用中,我们用“字符”出现的次数作为权. <2>  路径长度:可以理解成该节点到根节点的层数,比如:“A”到根节点

经典算法题每日演练——第五题 字符串相似度

原文:经典算法题每日演练--第五题 字符串相似度 这篇我们看看最长公共子序列的另一个版本,求字符串相似度(编辑距离),我也说过了,这是一个非常实用的算法,在DNA对比,网 页聚类等方面都有用武之地. 一:概念 对于两个字符串A和B,通过基本的增删改将字符串A改成B,或者将B改成A,在改变的过程中我们使用的最少步骤称之为“编辑距离”. 比如如下的字符串:我们通过种种操作,痉挛之后编辑距离为3,不知道你看出来了没有? 二:解析 可能大家觉得有点复杂,不好理解,我们试着把这个大问题拆分掉,将"字符串

经典算法题每日演练——第二十三题 鸡尾酒排序

原文:经典算法题每日演练--第二十三题 鸡尾酒排序 这篇我们继续扯淡一下鸡尾酒排序,为了知道为啥取名为鸡尾酒,特意看了下百科,见框框的话,也只能勉强这么说了. 要是文艺点的话,可以说是搅拌排序,通俗易懂点的话,就叫“双向冒泡排序”,我想作为码农的话,不可能不知道冒泡排序, 冒泡是一个单向的从小到大或者从大到小的交换排序,而鸡尾酒排序是双向的,从一端进行从小到大排序,从另一端进行从大 到小排序. 从图中可以看到,第一次正向比较,我们找到了最大值9. 第一次反向比较,我们找到了最小值1. 第二次正向

经典算法题每日演练——第十题 树状数组

原文:经典算法题每日演练--第十题 树状数组 有一种数据结构是神奇的,神秘的,它展现了位运算与数组结合的神奇魅力,太牛逼的,它就是树状数组,这种数据结构不是神人是发现不了的. 一:概序 假如我现在有个需求,就是要频繁的求数组的前n项和,并且存在着数组中某些数字的频繁修改,那么我们该如何实现这样的需求?当然大家可以往 真实项目上靠一靠. ① 传统方法:根据索引修改为O(1),但是求前n项和为O(n). ②空间换时间方法:我开一个数组sum[],sum[i]=a[1]+....+a[i],那么有点意