Dijkstra算法初探

近期在优快云推文中看到Dijkstra相关信息,抱着好奇的心态,打算用Kotlin语言在Android上面对其进行学习和实现,以便能更好的了解和学习Dijkstra算法。

什么是Dijkstra算法

Dijkstra算法是由荷兰计算机科学家艾兹格·戴克斯特拉(Edsger W. Dijkstra)在1956年提出的,用于在图中找到单个源点到所有其他顶点的最短路径的算法。这个算法特别适用于具有非负边权重的图,它可以有效地解决单源最短路径问题(Single-Source Shortest Path Problem, SSSP)。Dijkstra算法在许多领域都有应用,例如在网络路由在网络中找到数据传输的最短路径或者在地图服务中计算两点之间的最短行驶路线等。Dijkstra算法因其简单性和有效性而被广泛使用,是图论和算法设计中的一个重要基础。

Dijkstra算法原理

Dijkstra算法的核心思想是贪心策略,它维护一个顶点集合,这个集合中的顶点都是从源点出发已知最短路径的顶点。算法从一个顶点开始,逐步扩展到图中的其他顶点,直到找到从源点到所有顶点的最短路径。

贪心策略(Greedy Strategy)是一种在算法设计中常用的方法,它在每一步选择中都采取当前状态下最优的选择,以期望通过局部最优的选择达到全局最优的结果。贪心算法不保证总是能得到全局最优解,但在某些问题上,贪心策略可以产生最优解。

Dijkstra实现

1.算法实现步骤

初始化: 将起点到自己的距离设为0,到其他所有点的距离设为无穷大。

选择最近点: 从所有未被探索的点中,选择一个距离起点最近的点。

更新距离: 对于这个最近点,检查通过这个点到其他所有点的距离是否会更短。如果是,更新这些点的距离。

标记最近点为已探索: 将这个最近点标记为已探索。

重复: 重复步骤2-4,直到所有的点都被探索过,或者你已经找到了到达目标点的最短路径。

2.代码实现

我们首先需要创建一个名为Graph的类,用来封装和实施Dijkstra算法。下面我们将通过一步步完善这个类来学习Dijkstra的实现,首先让我们加入如下代码。

class Graph(val vertices: Int) {

    private val adjMatrix = Array(vertices) { FloatArray(vertices) }

    init {
        for (i in 0 until vertices) {
            for (j in 0 until vertices) {
                if (i == j) adjMatrix[i][j] = 0.0f 
                else adjMatrix[i][j] = Float.MAX_VALUE
            }
        }
    }

在Graph类有一个需要传入Int参数的构造函数,传入的这个参数是图的顶点数。

接着我们创建了一个名为adjMatrix的二维数组,这个数组将用来表示一个邻接矩阵。

邻接矩阵是实现图的顶点和边权重一一对应的常用数据结构。对于有 n 个顶点的图,邻接矩阵是一个 n×n 的二维数组,其中每个元素adjMatrix[i][j] 表示从顶点 i 到顶点 j 的边的权重。如果没有直接的边,则权重可以设置为无穷大(Float.MAX_VALUE 或 Integer.MAX_VALUE)。

这里又继续对adjMatrix做了初始化,初始化的时候如果是自己到自己的顶点时权重默认设置为0,如果是到其他顶点的话,权重全部设置为了Float.MAX_VALUE用来表示该顶点无法连接到。

现在已经有了邻接矩阵并且进行了初始化,那么接下来,我们需要一个函数用来给邻接矩阵添加数据,因此让我们继续添加如下代码:

    fun addEdge(u: Int, v: Int, weight: Float) {
        adjMatrix[u][v] = weight
    }

这个函数看起来相当简单,实现了在adjMatrix中添加从u点到v点的权重值weight。

现在一切准备就绪,让我们来着手实现Dijkstra算法。

    fun dijkstra(src: Int): FloatArray {
        val dist = FloatArray(vertices){Float.MAX_VALUE}
        val visited = BooleanArray(vertices){false}

        dist[src] = 0.0f//源点到自己的距离始终为0

dijkstra函数有一个Int值的参数src,这个参数表示源点,也就是dijkstra算法将会以这个点为起点来计算到达其他顶点的最短路径。

接着在函数的顶部我们创建了两个一维数组并进行了初始化,dist数组用来保存算法过程中源点到其他顶点当前已知的最优结果,其元素被初始化为了Float.MAX_VALUE,主要是为了方便后续计算;visited数组主要用于跟踪每个顶点是否已经被访问过。dist和visited的索引则表示对应的顶点。

最后设置源点到自己的距离为0,所有顶点到自己的距离始终为0,这个上面也有讲到。接下来让我们继续完善。

        for (i in 0 until vertices) {
            var minDistance = Float.MAX_VALUE
            var u = -1

            for (v in 0 until vertices) {
                if (!visited[v] && dist[v] <= minDistance) {
                    minDistance = dist[v]
                    u = v
                }
            }

            if (u == -1) break

            visited[u] = true

外围循环依次遍历所有顶点,内部循环通过每次遍历所有顶点,在未访问的顶点中找出当前最短的路径顶点。如果最短路径顶点没找到,表示算法执行结束,则退出算法。如果找到了最短路径的顶点,那么就将该顶点的状态置为已访问。

现在通过上面代码,我们已经找到了最短路径顶点,那么接下来,就要通过该顶点,来更新到它最近节点的距离,让我们继续该函数的最后一段代码:

            for (v in 0 until vertices) {
                if (!visited[v] && adjMatrix[u][v] != Float.MAX_VALUE && dist[u] != Float.MAX_VALUE
                    && dist[u] + adjMatrix[u][v] < dist[v]) {
                    dist[v] = dist[u] + adjMatrix[u][v]
                }
            }
        }

        return dist

这里还是一个循环,这块逻辑其实也不难,主要就是从未访问的顶点中找到一个临近可达的顶点,然后判断源点到该顶点的距离是否小于当前过程中计算的距离,如果是则进行替换更新。

至此Dijkstra算法已经实现完毕,那么接下来我们来再写一段验证代码,对其准确性做一个验证。

fun main() {
    val g = Graph(9)
    g.addEdge(0, 1, 4f)
    g.addEdge(0, 7, 8f)
    g.addEdge(1, 2, 8f)
    g.addEdge(1, 7, 11f)
    g.addEdge(2, 3, 7f)
    g.addEdge(2, 8, 2f)
    g.addEdge(2, 5, 4f)
    g.addEdge(3, 4, 9f)
    g.addEdge(3, 5, 14f)
    g.addEdge(4, 5, 10f)
    g.addEdge(5, 6, 2f)
    g.addEdge(6, 7, 1f)
    g.addEdge(6, 8, 6f)
    g.addEdge(7, 8, 7f)

    val src = 0
    val distances = g.dijkstra(src)

    println("Vertex Distance from Source")
    for (i in 0 until g.vertices) {
        println("$i \t\t ${if (distances[i] == Float.MAX_VALUE) "∞" else distances[i]}")
    }
}

main函数构建了一个具体的图实例,添加了一系列边,并调用dijkstra方法来计算从源点(顶点0)出发到其他顶点的最短路径长度。最后,程序打印出每个顶点到源点的最短距离,如果某个顶点不可达,则显示为"∞"。

运行后结果:

在这里插入图片描述

这么看好像不太容易看出它结果的准确性,那么让我们把他转化为表格来看看。

在这里插入图片描述

如果还觉得不太容易看的话,让我们把他转化为连线图来看。
在这里插入图片描述

这下应该就可以清晰的看到结果和我们观察一致,说明我们的算法是正确的。

写到最后

由于工作性质吧,笔者平时很少接触算法,之前也有自学过,看过一些书籍,但是平时用不到所以就基本上也忘记的差不多了,因此本篇文章如果有不对的地方也希望有人能够帮忙指正,
对于文章中的代码其实量不大,我之所以没有一次给出来是因为个人觉得分段提供可以按照我写代码的思路来书写本篇文章,再一个也是觉得这样可能会更容易让有需要的同学理解。
如果有更好的建议,也希望大牛能够不吝赐教,毕竟我也写文章功力也尚浅,但是还得坚持,因为写文章是梳理和加深知识印象最好的手段,不容置疑,哈哈~。好了就先到这,感谢大家观看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值