Day_38Dijkstra 算法与 Prim 算法

目录

一. 关于Dijkstra 算法与 Prim 算法

        1. 关于Dijkstra 算法

        2.  关于Prim 算法

二. 如何实现Dijkstra 算法与 Prim 算法

        1. Dijkstra算法的实现

        2. Prim算法的实现

三. 代码实现

        1. Dijkstra算法

        1.1 Dijkstra的初始化:

        1.2 Dijkstra的核心代码:

        2. Prim算法        

        2.1 Prim算法的初始化:

        2.2 Prim算法的核心代码:

四. 代码展示

五. 数据测试

        Dijkstra的测试结果:

        Prim的测试结果:      

六. 总结与反思


一. 关于Dijkstra 算法与 Prim 算法

        1. 关于Dijkstra 算法

        当图是带权图时,把从一个顶点v_{0}到图中其余任意一个顶点v_{i}的一条路径(可能不止一条)所经过边上的权值之和,定义为该路径的带权路径长度,把带权路径长度最短的那条路径成为最短路径,求解最短路径的算法通常都依赖于一种性质,即两点之间的最短路径也包含了路径上其他顶点间的最短路径。带权有向图G的最短路径问题一般可分为两类:一是单源最短路径,即求图中某一顶点到其他各顶点的最短路径,可通过Dijkstra算法求解;二是求每对顶点间的最短路径,可通过Floyd算法来求解,针对于Floyd算法我在这一小节不进行讨论,有兴趣的读者欢迎看数据结构图这一部分。

        2.  关于Prim 算法

        一个连通图的最小生成树包含图的所有顶点,并且只含尽可能少的边。对于生成树来说,若砍去它的一条边,则会使生成树变成非连通图;若给它增加一条边,则会形成图中的一条回路。对于一个带权连通无向图G=(V,E),生成树不同,每棵树的全(即树中所有边上的权值之和)也可能不同。设O是G的所有生成树的集合,若T为O中边的权值之和最小的那颗生成树,则T称为G的最小生成树。

        不难看出,最小生成树具有如下性质:

        最小生成树不是惟一的,即最小生成树的树形不唯一,O中可能有多个最小生成树。当图G中的各边权值互不相等时,G的最小生成树是惟一的;若无向连通图G的边数比顶点数少1,即G本身是一棵树时,则G的最小生成树就是它本身。

        最小生成树的边的权值之和总是唯一的,虽然最小生成树不唯一,但其对应边的权值之和总是唯一的,而且是最小的。

        最小生成树的边数为顶点数减1。

        基于以上三条性质的最小生成树算法主要有Prim算法,它是基于贪心算法的策略。

二. 如何实现Dijkstra 算法与 Prim 算法

        1. Dijkstra算法的实现

        Dijkstra算法设置一个集合S记录已求的的最短路径的顶点,初始时把源点v_{0}放入S,集合S每并入一个新的顶点v_{i},都要修改源点v_{0}到集合V-S中顶点当前的最短路径长度值(这里可能不太好理解,没关系,等下就可以明白)。

        除此之外,构造两个矩阵:

        dist[ ]:记录从源点v_{0}到其他各个顶点当前的最短路径长度,它的初始化为:从v_{0}v_{i}的邻接矩阵的值(第v_{0}行的值),当然若v_{0}暂时没有到其他各个节点的路径,则初始化为∞。

        path[ ]:path[ i ]表示从源点v_{0},到顶点v_{i}之间的最短路径的前驱节点,在算法结束时,可根据其值追溯得到源点v_{0}到顶点v_{i}的最短路径。

        假设从顶点0出发即v_{0}=0,集合S最初只包含顶点0,邻接矩阵arcs表示带权有向图,arcs[i][j]表示有向边<i,j>的权值,若不存在有向边<i,j>,则arcs[i][j]=∞。

        Dijkstra算法步骤如下:

        step1:初始化集合S初始化为{0},dist[ ]的初始值为dist[i]=arcs[0][i],i=1,2...n-1。

        step2:从集合V-S中选出v_{j},满足dist[j]=min{dist[i] | v_{i}\inV-S},v_{j}就是当前求得的一条从v_{0}出发的最短路径的终点,令S=S∪{ v_{j} }。

        step3:修改从v_{0}出发到集合V-S上任意一个顶点v_{k}可达的最短路径长度:若dist[j]+arcs[j][k]<dist[k],则更新dist[k]=dist[j]+arcs[j][k]。

        step4:重复step2-step3操作共n-1次,直到所有的顶点都包含在S中。

        (ps:step3就是当一个新的节点并入集合S后,修改dist矩阵最短路径长度的值)

        为了更清晰的表述先有如下图H所示,求v1源点到各个点的最短路径

图H

        初始化集合S中有{v1},dist矩阵和path矩阵如下图H1所示。

        这时选取最小的dist[i]=5,故我们现在已经可以确定源点1到节点5的最小路径是5,路径是1->5,这时将5号节点纳入集合S{v1,v5},接着我们更新dist矩阵和path矩阵如图H2所示(由于v2,v3,v4都改变了dist的值,所以他们的前驱节点都应该改变为5,这一点非常重要,后面我们也会有不改变前驱节点的情况,那就是由于在这次循环中没有改变dist矩阵的值)。

图H1
图H2

        这时选取最小的dist[i]=7,确定源点1到节点4的最小路径是7,路径是1->5->4这时将4号节点纳入集合S{v1,v5,v4},接着我们更新dist矩阵和path矩阵如图H3所示,这里我们就没有改变2号节点的值,这是由于引入节点4之后,并没有使节点2到源点1的距离变得更小,于是我们就没有改变2号节点的前驱节点,即没有改变path矩阵2号节点的值。

        这时选取最小的dist[i]=8,确定源点1到节点2的最小路径是8,路径是1->5->2这时将2号节点纳入集合S{v1,v5,v4,v2},接着我们更新dist矩阵和path矩阵如图H4所示。

图H3
图H4

        这时选取最小的dist[i]=9,确定源点1到节点3的最小路径是9,路径是1->5->2->3这时将3号节点纳入集合S{v1,v5,v4,v2,v3},故最终我们完成了图H的Dijkstra算法,并且得到了源点1到各个节点的最小路径长度和源点1到各个节点的路径。

        2. Prim算法的实现

        Prim算法构造最小生成树的过程如图P1-P6所示。初始时,从图中任取一个顶点加入树T,此时树中只含有一个顶点,之后选择一个与当前T中顶点集合距离最近的顶点,并将该顶点和对应的边加入T,每次操作之后T中的顶点数和边数都增加1。以此推类,直到图中所有的顶点都并入T,得到的T就是最小生成树。此时T中必然有n-1条边。

图P1
图P2
图P3

图P4
图P5
图P6

        算法步骤:

        初始化一个树T;

        添加任意一个顶点U;

        while(树T中不含全部顶点){

                当(u,v)是权值最小的边:

                        边归入树;

                        顶点归入树;

        }

三. 代码实现

        1. Dijkstra算法

        1.1 Dijkstra的初始化:

        构造距离矩阵tempDistanceArray,其初始值等于第1号节点直接到各个节点的值,构造路径矩阵tempParentArray,初始值除了1号节点,1号节点的值设置为-1,其他的都设置为1(若邻接矩阵有1号节点不能直接到达的节点,则设置为-1),构造一个访问矩阵(相当于集合S的作用),用来存储某个节点是否已经接入集合S。

        // Step 1. Initialize.
        int[] tempDistanceArray = new int[numNodes];
        for (int i = 0; i < numNodes; i++) {
            tempDistanceArray[i] = weightMatrix.getValue(paraSource, i);
        } // Of for i

        int[] tempParentArray = new int[numNodes];
        Arrays.fill(tempParentArray, paraSource);
        // -1 for no parent.
        tempParentArray[paraSource] = -1;

        // Visited nodes will not be considered further.
        boolean[] tempVisitedArray = new boolean[numNodes];
        tempVisitedArray[paraSource] = true;

        1.2 Dijkstra的核心代码:

        由于总共有n个节点,但是要除开源节点,所以总共循环n-1次,临时变量tempMinDistance记录此时最短的路径长度,tempBestNode记录此时最短路径的前驱节点,开始第一层循环。首先设置tempMinDistance为∞,在tempDistanceArray矩阵里面寻找没有被访问,且距离值最小的节点,用tempBestNode记录节点的值,用tempMinDistance记录最短的路径,接着更新节点最短路径节点tempBestNode的访问位为true。开始更新下一个节点的情况。

        还是开始第三轮循环,这次循环的主要目的是将tempBestNode纳入集合S后下一个节点的路径情况和距离情况。寻找没有被访问过的节点,寻找且与更新节点tempBestNode直接相连的节点,当原来距离矩阵的tempDistanceArray[j](j是第三轮循环其他没有被访问的节点)>集合S到新节点tempBestNode的距离+新节点tempBestNode到 j 节点的距离(由于先前直接相连的判断,这时的距离是一个有限值),则更新距离矩阵tempDistanceArray[j]=集合S到新节点tempBestNode的距离+新节点tempBestNode到 j 节点的距离,并且节点 j 的前驱节点为tempBestNode,按照上述一直更新所有的节点,完成第三轮循环。

        // Step 2. Main loops.
        int tempMinDistance;
        int tempBestNode = -1;
        for (int i = 0; i < numNodes - 1; i++) {
            // Step 2.1 Find out the best next node.
            tempMinDistance = Integer.MAX_VALUE;
            for (int j = 0; j < numNodes; j++) {
                // This node is visited.
                if (tempVisitedArray[j]) {
                    continue;
                } // Of if

                if (tempMinDistance > tempDistanceArray[j]) {
                    tempMinDistance = tempDistanceArray[j];
                    tempBestNode = j;
                } // Of if
            } // Of for j

            tempVisitedArray[tempBestNode] = true;

            // Step 2.2 Prepare for the next round.
            for (int j = 0; j < numNodes; j++) {
                // This node is visited.
                if (tempVisitedArray[j]) {
                    continue;
                } // Of if

                // This node cannot be reached.
                if (weightMatrix.getValue(tempBestNode, j) >= MAX_DISTANCE) {
                    continue;
                } // Of if

                if (tempDistanceArray[j] > tempDistanceArray[tempBestNode]
                        + weightMatrix.getValue(tempBestNode, j)) {
                    // Change the distance.
                    tempDistanceArray[j] = tempDistanceArray[tempBestNode]
                            + weightMatrix.getValue(tempBestNode, j);
                    // Change the parent.
                    tempParentArray[j] = tempBestNode;
                } // Of if
            } // Of for j

        2. Prim算法        

        2.1 Prim算法的初始化:

        初始化和Dijkstra几乎一致(默认从0号节点开始计算),构建距离矩阵(邻接矩阵的第0行),前驱矩阵(0列为-1,其他列都为0),访问矩阵(0列设置为已访问,其他列设置为未访问)。

        // Step 1. Initialize.
        // Any node can be the source.
        int tempSource = 0;
        int[] tempDistanceArray = new int[numNodes];
        for (int i = 0; i < numNodes; i++) {
            tempDistanceArray[i] = weightMatrix.getValue(tempSource, i);
        } // Of for i

        int[] tempParentArray = new int[numNodes];
        Arrays.fill(tempParentArray, tempSource);
        // -1 for no parent.
        tempParentArray[tempSource] = -1;

        // Visited nodes will not be considered further.
        boolean[] tempVisitedArray = new boolean[numNodes];
        tempVisitedArray[tempSource] = true;

        2.2 Prim算法的核心代码:

        大循环(第一轮循环)循环n-1次(总共有n个节点,除了源节点,故次数为n-1),在距离矩阵里面寻找没有被访问过,且距离最小的节点,用tempBestNode记录节点,tempMinDistance记录最小的距离。找到最小的节点,改变访问矩阵的值tempVisitedArray[tempBestNode] = true。接着改变并入节点tempBestNode的情况,第三轮循环:如果距离矩阵tempDistanceArray的某个节点j 对应的值>从节点tempBestNode到节点 j 的值,则改距离矩阵tempDistanceArray[j]的值等于邻接矩阵tempBestNode行,j列的值。j节点的前驱为tempBestNode(empParentArray[j] = tempBestNode)。按照上述过程一直直到大循环结束,得到最终结果。

        // Step 2. Main loops.
        int tempMinDistance;
        int tempBestNode = -1;
        for (int i = 0; i < numNodes - 1; i++) {
            // Step 2.1 Find out the best next node.
            tempMinDistance = Integer.MAX_VALUE;
            for (int j = 0; j < numNodes; j++) {
                // This node is visited.
                if (tempVisitedArray[j]) {
                    continue;
                } // Of if

                if (tempMinDistance > tempDistanceArray[j]) {
                    tempMinDistance = tempDistanceArray[j];
                    tempBestNode = j;
                } // Of if
            } // Of for j

            tempVisitedArray[tempBestNode] = true;

            // Step 2.2 Prepare for the next round.
            for (int j = 0; j < numNodes; j++) {
                // This node is visited.
                if (tempVisitedArray[j]) {
                    continue;
                } // Of if

                // This node cannot be reached.
                if (weightMatrix.getValue(tempBestNode, j) >= MAX_DISTANCE) {
                    continue;
                } // Of if

                // Attention: the difference from the Dijkstra algorithm.
                if (tempDistanceArray[j] > weightMatrix.getValue(tempBestNode, j)) {
                    // Change the distance.
                    tempDistanceArray[j] = weightMatrix.getValue(tempBestNode, j);
                    // Change the parent.
                    tempParentArray[j] = tempBestNode;
                } // Of if
            } // Of for j

            // For test
            System.out.println(
                    "The selected distance for each node: " + Arrays.toString(tempDistanceArray));
            System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
        } // Of for i

四. 代码展示

        主类:

package Day_38;
public class deom1 {
    /**
     *********************
     * The entrance of the program.
     *
     * @param args
     *            Not used now.
     *********************
     */
    public static void main(String args[]) {
        int MAX_DISTANCE = 1000;
        Net tempNet0 = new Net(3);
        System.out.println(tempNet0);

        int[][] tempMatrix1 = { { 0, 9, 3, 6 }, { 5, 0, 2, 4 }, { 3, 2, 0, 1 }, { 2, 8, 7, 0 } };
        Net tempNet1 = new Net(tempMatrix1);
        System.out.println(tempNet1);

        // Dijkstra
        tempNet1.dijkstra(1);

        // An undirected net is required.
        int[][] tempMatrix2 = { { 0, 7, MAX_DISTANCE, 5, MAX_DISTANCE }, { 7, 0, 8, 9, 7 },
                { MAX_DISTANCE, 8, 0, MAX_DISTANCE, 5 }, { 5, 9, MAX_DISTANCE, 0, 15 },
                { MAX_DISTANCE, 7, 5, 15, 0 } };
        Net tempNet2 = new Net(tempMatrix2);
        tempNet2.prim();
    }// Of main

}

        调用类:

package Day_38;

import Day_31.IntMatrix;

import java.util.Arrays;

/**
 * Weighted graphs are called nets.
 *
 * @author An Jian 2569222191@qq.com.
 */
public class Net {

    /**
     * The maximal distance. Do not use Integer.MAX_VALUE.
     */
    public static final int MAX_DISTANCE = 10000;

    /**
     * The number of nodes.
     */
    int numNodes;

    /**
     * The weight matrix. We use int to represent weight for simplicity.
     */
    IntMatrix weightMatrix;

    /**
     * ********************
     * The first constructor.
     *
     * @param paraNumNodes The number of nodes in the graph.
     *                     ********************
     */
    public Net(int paraNumNodes) {
        numNodes = paraNumNodes;
        weightMatrix = new IntMatrix(numNodes, numNodes);
        for (int i = 0; i < numNodes; i++) {
            // For better readability, you may need to write fill() in class
            // IntMatrix.
            Arrays.fill(weightMatrix.getData()[i], MAX_DISTANCE);
        } // Of for i
    }// Of the first constructor

    /**
     * ********************
     * The second constructor.
     *
     * @param paraMatrix The data matrix.
     *                   ********************
     */
    public Net(int[][] paraMatrix) {
        weightMatrix = new IntMatrix(paraMatrix);
        numNodes = weightMatrix.getRows();
    }// Of the second constructor

    /**
     * ********************
     * Overrides the method claimed in Object, the superclass of any class.
     * ********************
     */
    public String toString() {
        String resultString = "This is the weight matrix of the graph.\r\n" + weightMatrix;
        return resultString;
    }// Of toString

    /**
     * ********************
     * The Dijkstra algorithm: shortest path from the source to all nodes.
     *
     * @param paraSource The source node.
     * @return The distances to all nodes.
     * ********************
     */
    public int[] dijkstra(int paraSource) {
        // Step 1. Initialize.
        int[] tempDistanceArray = new int[numNodes];
        for (int i = 0; i < numNodes; i++) {
            tempDistanceArray[i] = weightMatrix.getValue(paraSource, i);
        } // Of for i

        int[] tempParentArray = new int[numNodes];
        Arrays.fill(tempParentArray, paraSource);
        // -1 for no parent.
        tempParentArray[paraSource] = -1;

        // Visited nodes will not be considered further.
        boolean[] tempVisitedArray = new boolean[numNodes];
        tempVisitedArray[paraSource] = true;

        // Step 2. Main loops.
        int tempMinDistance;
        int tempBestNode = -1;
        for (int i = 0; i < numNodes - 1; i++) {
            // Step 2.1 Find out the best next node.
            tempMinDistance = Integer.MAX_VALUE;
            for (int j = 0; j < numNodes; j++) {
                // This node is visited.
                if (tempVisitedArray[j]) {
                    continue;
                } // Of if

                if (tempMinDistance > tempDistanceArray[j]) {
                    tempMinDistance = tempDistanceArray[j];
                    tempBestNode = j;
                } // Of if
            } // Of for j

            tempVisitedArray[tempBestNode] = true;

            // Step 2.2 Prepare for the next round.
            for (int j = 0; j < numNodes; j++) {
                // This node is visited.
                if (tempVisitedArray[j]) {
                    continue;
                } // Of if

                // This node cannot be reached.
                if (weightMatrix.getValue(tempBestNode, j) >= MAX_DISTANCE) {
                    continue;
                } // Of if

                if (tempDistanceArray[j] > tempDistanceArray[tempBestNode]
                        + weightMatrix.getValue(tempBestNode, j)) {
                    // Change the distance.
                    tempDistanceArray[j] = tempDistanceArray[tempBestNode]
                            + weightMatrix.getValue(tempBestNode, j);
                    // Change the parent.
                    tempParentArray[j] = tempBestNode;
                } // Of if
            } // Of for j

            // For test
            System.out.println("The distance to each node: " + Arrays.toString(tempDistanceArray));
            System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
        } // Of for i

        // Step 3. Output for debug.
        System.out.println("Finally");
        System.out.println("The distance to each node: " + Arrays.toString(tempDistanceArray));
        System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
        return tempDistanceArray;
    }// Of dijkstra

    /**
     * ********************
     * The minimal spanning tree.
     *
     * @return The total cost of the tree.
     * ********************
     */
    public int prim() {
        // Step 1. Initialize.
        // Any node can be the source.
        int tempSource = 0;
        int[] tempDistanceArray = new int[numNodes];
        for (int i = 0; i < numNodes; i++) {
            tempDistanceArray[i] = weightMatrix.getValue(tempSource, i);
        } // Of for i

        int[] tempParentArray = new int[numNodes];
        Arrays.fill(tempParentArray, tempSource);
        // -1 for no parent.
        tempParentArray[tempSource] = -1;

        // Visited nodes will not be considered further.
        boolean[] tempVisitedArray = new boolean[numNodes];
        tempVisitedArray[tempSource] = true;

        // Step 2. Main loops.
        int tempMinDistance;
        int tempBestNode = -1;
        for (int i = 0; i < numNodes - 1; i++) {
            // Step 2.1 Find out the best next node.
            tempMinDistance = Integer.MAX_VALUE;
            for (int j = 0; j < numNodes; j++) {
                // This node is visited.
                if (tempVisitedArray[j]) {
                    continue;
                } // Of if

                if (tempMinDistance > tempDistanceArray[j]) {
                    tempMinDistance = tempDistanceArray[j];
                    tempBestNode = j;
                } // Of if
            } // Of for j

            tempVisitedArray[tempBestNode] = true;

            // Step 2.2 Prepare for the next round.
            for (int j = 0; j < numNodes; j++) {
                // This node is visited.
                if (tempVisitedArray[j]) {
                    continue;
                } // Of if

                // This node cannot be reached.
                if (weightMatrix.getValue(tempBestNode, j) >= MAX_DISTANCE) {
                    continue;
                } // Of if

                // Attention: the difference from the Dijkstra algorithm.
                if (tempDistanceArray[j] > weightMatrix.getValue(tempBestNode, j)) {
                    // Change the distance.
                    tempDistanceArray[j] = weightMatrix.getValue(tempBestNode, j);
                    // Change the parent.
                    tempParentArray[j] = tempBestNode;
                } // Of if
            } // Of for j

            // For test
            System.out.println(
                    "The selected distance for each node: " + Arrays.toString(tempDistanceArray));
            System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
        } // Of for i

        int resultCost = 0;
        for (int i = 0; i < numNodes; i++) {
            resultCost += tempDistanceArray[i];
        } // Of for i

        // Step 3. Output for debug.
        System.out.println("Finally");
        System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
        System.out.println("The total cost: " + resultCost);

        return resultCost;
    }// Of prim

}// Of class Net

五. 数据测试

        第一个Dijkstra方法的测试矩阵如下图G1所示,第二个Prim方法的测试矩阵如下如G2所示

图G1
图G2

        Dijkstra的测试结果:

        Prim的测试结果:      

六. 总结与反思

        Dijkstra算法和Prim算法本质上是用贪心算法,在不断的迭代中找到最优解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值