C#-数据结构-图的应用 最短路径/狄克斯特拉(Dikastra)算法/拓扑排序

6.4.2 最短路径

1、短路径的概念 

短路径问题是图的又一个比较典型的应用问题。例如,n 个城市之间的一 个公路网,给定这些城市之间的公路的距离,能否找到城市 A 到城市 B 之间一 条距离近的通路呢?如果城市用顶点表示,城市间的公路用边表示,公路的长 度作为边的权值。那么,这个问题就可归结为在网中求顶点 A 到顶点 B 的所有 路径中边的权值之和小的那一条路径,这条路径就是两个顶点之间的短路径 (Shortest Path),并称路径上的第一个顶点为源点(Source),后一个顶点为终 点(Destination)。在不带权的图中,短路径是指两个顶点之间经历的边数 少的路径。

短路径可以是求某个源点出发到其它顶点的短路径,也可以是求网中任 意两个顶点之间的短路径。这里只讨论单源点的短路径问题,感兴趣的读者 可参考有关文献,了解每一对顶点之间的短路径

网分为无向网和有向网,当把无向网中的每一条边(vi,vj)都定义为弧 和弧,则有向网就变成了无向网。因此,不失一般性,我们这里只讨论有 向网上的短路径问题。 

图 6.17 是一个有向网及其邻接矩阵。该网从顶点 A 到顶点 D 有 4 条路径, 分别是:路径(A,D),其带权路径长度为 30;路径(A,C,F,D),其带权路径长 度为 22;路径(A,C,B,E,D),其带权路径长度为 32;路径(A,C,F,E,D),其带权 路径长度为 34。路径(A,C,F,D)称为短路径,其带权路径长度 22 称为短距离。 

C#-数据结构-图的应用 最短路径/狄克斯特拉(Dikastra)算法/拓扑排序_第1张图片

2、狄克斯特拉(Dikastra)算法 

对于求单源点的短路径问题,狄克斯特拉(Dikastra)提出了一个按路径 长度递增的顺序逐步产生短路径的构造算法。狄克斯特拉的算法思想是:设置 两个顶点的集合 S 和 T,集合 S 中存放已找到短路径的顶点,集合 T 中存放当 前还未找到短路径的顶点。初始状态时,集合 S 中只包含源点,设为 v0,然 后从集合 T 中选择到源点 v0 路径长度短的顶点 u 加入到集合 S 中,集合 S 中 每加入一个新的顶点 u 都要修改源点 v0 到集合 T 中剩余顶点的当前短路径长 度值,集合 T 中各顶点的新的短路径长度值为原来的当前短路径长度值与 从源点过顶点 u 到达该顶点的新的短路径长度中的较小者。此过程不断重复, 直到集合 T 中的顶点全部加到集合 S 中为止。 

【例 6-5】以图 6.17 为例说明用狄克斯特拉算法求有向网的从某个顶点到其 余顶点短路径的过程

图6.18(a)~(f)给出了狄克斯特拉算法求从顶点A到其余顶点的短路径的过 程。图中虚线表示当前可选择的边,实线表示算法已确定包括到集合 S 中所有顶 点所对应的边。 

第一步:列出顶点 A 到其余各顶点的路径长度,它们分别为 0、∞、5、30、 ∞、∞。从中选取路径长度小的顶点 C(从源点到顶点 C 的短路径为 5)。 

第二步:找到顶点 C 后,再观察从源点经顶点 C 到各个顶点的路径是否比 第一步所找到的路径要小(已选取的顶点则不必考虑),可发现,源点到顶点 B 的路径长度更新为 20(A,C,B),源点到顶点 F 的路径长度更新为 12(A,C, F),其余的路径则不变。然后,从已更新的路径中找出路径长度小的顶点 F(从 源点到顶点 F 的短路径为 12)。 

第三步:找到顶点 C、F 以后,再观察从源点经顶点 C、F 到各顶点的路径 是否比第二步所找到的路径要小(已被选取的顶点不必考虑),可发现,源点到 顶点 D 的路径长度更新为 22(A,C,F,D),源点到顶点 E 的路径长度更新为 30(A,C,F,E),其余的路径不变。然后,从已更新的路径中找出路径长短 小的顶点 D(从源点到顶点 D 的短路径为 22)。 

第四步:找到顶点 C、F、D 后,现在只剩下后一个顶点 E 没有找到短 路径了,再观察从源点经顶点 C、F、D 到顶点 E 的路径是否比第三步所找到的路径要小(已选取的顶点则不必考虑),可以发现,源点到顶点 E 的路径长度更 新为 28(A,B,E),其余的路径则不变。然后,从已更新的路径中找出路径长 度小的顶点 E(从源点到顶点 E 的短路径为 28)。 

C#-数据结构-图的应用 最短路径/狄克斯特拉(Dikastra)算法/拓扑排序_第2张图片

C#-数据结构-图的应用 最短路径/狄克斯特拉(Dikastra)算法/拓扑排序_第3张图片

3、有向网的邻接矩阵类的实现 

本文以有向网的邻接矩阵类 DirecNetAdjMatrix来实现狄克斯特拉算法。 DirecNetAdjMatrix有三个成员字段,一个是 Node类型的一维数组 nodes, 存放有向网中的顶点信息,一个是整型的二维数组 matirx,表示有向网的邻接矩 阵,存放弧的信息,一个是整数 numArcs,表示有向网中弧的数目,有向网的邻 接矩阵类 DirecNetAdjMatrix的实现如下所示。 

 

public class DirecNetAdjMatrix : IGraph
{
    private Node[] nodes;      //有向网的顶点数组 
    private int numArcs;          //弧的数目
    private int[,] matrix;       //邻接矩阵数组 

    //构造器 
    public DirecNetAdjMatrix(int n)
    {
        nodes = new Node[n];
        matrix = new int[n, n];
        numArcs = 0;
    }

    //获取索引为index的顶点的信息 
    public Node GetNode(int index)
    {
        return nodes[index];
    }

    //设置索引为index的顶点的信息 
    public void SetNode(int index, Node v)
    {
        nodes[index] = v;
    }

    //弧数目属性 
    public int NumArcs
    {
        get
        {
            return numArcs;
        }
        set
        {
            numArcs = value;
        }
    }

    //获取matrix[index1, index2]的值         
    public int GetMatrix(int index1, int index2)
    {
        return matrix[index1, index2];
    }

    //设置matrix[index1, index2]的值 
    public void SetMatrix(int index1, int index2, int v)
    {
        matrix[index1, index2] = v;
    }

    //获取顶点数目 
    public int GetNumOfVertex()
    {
        return nodes.Length;
    }

    //获取弧的数目 
    public int GetNumOfEdge()
    {
        return numArcs;
    }

    //判断v是否是网的顶点 
    public bool IsNode(Node v)
    {
        //遍历顶点数组 
        foreach (Node nd in nodes)
        {
            //如果顶点nd与v相等,则v是图的顶点,返回true 
            if (v.Equals(nd))
            {
                return true;
            }
        }

        return false;
    }

    //获取v在顶点数组中的索引 
    public int GetIndex(Node v)
    {
        int i = -1;

        //遍历顶点数组 
        for (i = 0; i < nodes.Length; ++i)
        {
            //如果顶点nd与v相等,则v是图的顶点,返回索引值 
            if (nodes[i].Equals(v))
            {
                return i;
            }
        }
        return i;
    }

    //在v1和v2之间添加权为v的弧 
    public void SetEdge(Node v1, Node v2, int v)
    {
        //v1或v2不是网的顶点 
        if (!IsNode(v1) || !IsNode(v2))
        {
            Debug.WriteLine("Node is not belong to Graph!"); return;
        }

        matrix[GetIndex(v1), GetIndex(v2)] = v;
        ++numArcs;
    }

    //删除v1和v2之间的弧 
    public void DelEdge(Node v1, Node v2)
    {
        //v1或v2不是网的顶点 
        if (!IsNode(v1) || !IsNode(v2))
        {
            Debug.WriteLine("Node is not belong to Graph!"); return;
        }

        //v1和v2之间存在弧 
        if (matrix[GetIndex(v1), GetIndex(v2)] != int.MaxValue)
        {
            matrix[GetIndex(v1), GetIndex(v2)] = int.MaxValue;
            --numArcs;
        }
    }

    //判断v1和v2之间是否存在弧 
    public bool IsEdge(Node v1, Node v2)
    {
        //v1或v2不是网的顶点 
        if (!IsNode(v1) || !IsNode(v2))
        {
            Debug.WriteLine("Node is not belong to Graph!"); return false;
        }

        //v1和v2之间存在弧 
        if (matrix[GetIndex(v1), GetIndex(v2)] != int.MaxValue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

 

4狄克斯特拉算法的实现

 

为实现狄克斯特拉算法,引入两个数组,一个一维数组 ShortPathArr,用来保存从源点到各个顶点的 短路径的长度,一个二维数组 PathMatrixArr,用来保存从源点到某个顶点的 短路径上的顶点,如 PathMatrix[v][w]为 true,则 w 为从源点到顶点 v 的 短路径上的顶点。为了该算法的结果被其他算法使用,把这两个数组作为算法的参数使用。另外,为了表示某顶点的 短路径是否已经找到,在算法中设了一个一维数组 final,如果 final[i]为 true,则表示已经找到第 i 个顶点的 短路径。i 是该顶点在邻接矩阵中的序号。同样,把该算法作为类

DirecNetAdjMatrix的成员方法来实现。

  public void Dijkstra(ref bool[,] pathMatricArr, ref int[] shortPathArr, Node n)
    {
        int k = 0; bool[] final = new bool[nodes.Length];

        //初始化 
        for (int i = 0; i < nodes.Length; ++i)
        {
            final[i] = false; shortPathArr[i] = matrix[GetIndex(n), i];

            for (int j = 0; j < nodes.Length; ++j)
            {
                pathMatricArr[i, j] = false;
            }
            if (shortPathArr[i] != 0 && shortPathArr[i] < int.MaxValue)
            {
                pathMatricArr[i, GetIndex(n)] = true;
                pathMatricArr[i, i] = true;
            }
        }

        // n为源点 
        shortPathArr[GetIndex(n)] = 0; final[GetIndex(n)] = true;

        //处理从源点到其余顶点的 短路径 
        for (int i = 0; i < nodes.Length; ++i)
        {
            int min = int.MaxValue;

            //比较从源点到其余顶点的路径长度 
            for (int j = 0; j < nodes.Length; ++j)
            {
                //从源点到j顶点的 短路径还没有找到 
                if (!final[j])
                {
                    // 从源点到j顶点的路径长度 小
                    if (shortPathArr[j] < min)
                    {
                        k = j; min = shortPathArr[j];
                    }
                }
            }

            //源点到顶点k的路径长度 小 
            final[k] = true;

            //更新当前 短路径及距离 
            for (int j = 0; j < nodes.Length; ++j)
            {
                if (!final[j] && (min + matrix[k, j] < shortPathArr[j]))
                {
                    shortPathArr[j] = min + matrix[k, j]; for (int w = 0; w < nodes.Length; ++w)
                    {
                        pathMatricArr[j, w] = pathMatricArr[k, w];
                    }
                    pathMatricArr[j, j] = true;
                }
            }
        }
    }

 

 

6.4.3 拓扑排序

拓扑排序(Topological Sort)是图中重要的运算之一,在实际中应用很广泛。例如,很多工程都可分为若干个具有独立性的子工程,我们把这些子工程称为“活动”。每个活动之间有时存在一定的先决条件关系,即在时间上有着一定的相互制约的关系。也就是说,有些活动必须在其它活动完成之后才能开始,即某项活动的开始必须以另一项活动的完成为前提。在有向图中,若以图中的顶点表示活动,以弧表示活动之间的优先关系,这样的有向图称为 AOV 网(Active On Vertex Network)。

在 AOV 网中,若从顶点 vi 到顶点 vj 之间存在一条有向路径,则称 vi 是 vj 的前驱,vj 是 vi 的后继。若是 AOV 网中的弧,则称 vi 是 vj 的直接前驱, vj 是 vi 的直接后继。

 

例如,一个软件专业的学生必须学习一系列的基本课程(如表 6.2 所示)。其中,有些课程是基础课,如“高等数学”、“程序设计基础”,这些课程不需要先修课程,而另一些课程必须在先学完某些课程之后才能开始学习。如通常在学完“程序设计基础”和“离散数学”之后才开始学习“数据结构”等等。因此,

可以用 AOV 网来表示各课程及其之间的关系,如图 6.19 所示。

表 6.2                       软件专业必修课程

课程编号

课程名称

先决条件

c1

程序设计基础

c2

离散数学

c1

c3

数据结构

c1,c2

c4

汇编语言

c1

c5

语言的设计与实现

c3,c4

c6

计算机原理

c11

c7

编译原理

c3,c5

c8

操作系统

c3,c6

c9

高等数学

c10

线性代数

c9

c11

普通物理

c9

c12

数值分析

c9,c10,c11

在 AOV 网中,不应该出现有向环路,因为有环意味着某项活动以自己作为先决条件,这样就进入了死循环。如果图 6.19 的有向图出现了有向环路,则教学计划将无法编排。因此,对给定的 AOV 网应首先判定网中是否存在环。检测的办法是对有向图进行拓扑排序(Topological Sort),若网中所有顶点都在它的拓扑有序序列中,则 AOV 网中必定不存在环。

C#-数据结构-图的应用 最短路径/狄克斯特拉(Dikastra)算法/拓扑排序_第4张图片

实现一个有向图的拓扑有序序列的过程称为拓扑排序。可以证明,任何一个有向无环图,其全部顶点都可以排成一个拓扑序列,而其拓扑有序序列不一定是唯一的。例如,图 6.19 的有向图的拓扑有序序列有多个,这里列举两个如下:

(c1,c2,c3,c4,c5,c7,c8,c9,c10,c11,c6,c12,c8) 和  (c9,c10,c11,c6,c1,c12,c4,c2,c3,c5,c7,c8)

由上面两个序列可知,对于图中没有弧相连的两个顶点,它们在拓扑排序的序列中出现的次序没有要求。例如,第一个序列中 c1 先于 c9,第二个则反之。拓扑排序的任何一个序列都是一个可行的活动执行顺序,它可以检测到图中是否存在环,因为如果有环,则环中的顶点无法输出,所以得到的拓扑有序序列没有包含图中所有的顶点。

下面是拓扑排序算法的描述:

(1)在有向图中选择一个入度为 0 的顶点(即没有前驱的顶点),由于该顶 点没有任何先决条件,输出该顶点;

(2)从图中删除所有以它为尾的弧;

(3)重复执行(1)和(2),直到找不到入度为 0 的顶点,拓扑排序完成。
 

如果图中仍有顶点存在,却没有入度为 0 的顶点,说明 AOV 网中有环路,否则没有环路。

【例 6-6】以图 6.20(a)为例求出它的一个拓扑有序序列。

C#-数据结构-图的应用 最短路径/狄克斯特拉(Dikastra)算法/拓扑排序_第5张图片

第一步:在图 6.20(a)所示的有向图中选取入度为 0 的顶点 c4,删除顶点 c4 及与它相关联的弧,得到图 6.31(b)所示的结果,并得到第一个 拓扑有序序列顶点 c4。

第二步:再在图 6.20(b)中选取入度为 0 的顶点 c5,删除顶点 c5 及与它相关 联的弧,得到图 6.20(c)所示的结果,并得到两个拓扑有序序列顶点 c4, c5。

第三步:再在图 6.20(c)中选取入度为 0 的顶点 c1,删除顶点 c1 及与它相关 联的弧,得到图 6.20(d)所示的结果,并得到三个拓扑有序序列 顶点 c4,c5,c1。

第四步:再在图 6.20(d)中选取入度为 0 的顶点 c2,删除顶点 c2 及与它相关 联的弧,得到图 6.20(e)所示的结果,并得到四个拓扑有序序列顶点 c4, c5,c1,c2。

第五步:再在图 6.20(e)中选取入度为 0 的顶点 c3,删除顶点 c3 及与它相关 联的弧,得到图 6.20(f)所示的结果,并得到五个拓扑有序序列顶点 c4, c5,c1,c2,c3。

第六步:后选取仅剩下的后一个顶点 c6,拓扑排序结束,得到图 6.20(a)的一个拓扑有序序列(c4,c5,c1,c2,c3,c6)。 

小结

图是另一种比树形结构更复杂的非线性数据结构,图中的数据元素称为顶 点,顶点之间是多对多的关系。图分为有向图和无向图,带权值的图称为网。

图的存储结构很多,一般采用数组存储图中顶点的信息,邻接矩阵采用矩阵 也就是二维数组存储顶点之间的关系。无向图的邻接矩阵是对称的,所以在存储 时可以只存储上三角矩阵或下三角矩阵的数据;有向图的邻接矩阵不是对称的。 邻接表用一个链表来存储顶点之间的关系,所以邻接表是顺序存储与链式存储相 结合的存储结构。

图的遍历方法有两种:深度优先遍历和广度优先遍历。图的深度优先遍历类 似于树的先序遍历,是树的先序遍历的推广,它访问顶点的顺序是后进先出,与 栈一样。图的广度优先遍历类似于树的层序遍历,它访问顶点的顺序是先进先出, 与队列一样。

图的应用很广,本章重点介绍了三个方面的应用。小生成树是一个无向连 通网中边的权值总和小的生成树。构造小生成树必须包括 n 个顶点、n-1 条 边及不存在回路。构造小生成树的常用算法有普里姆算法和克鲁斯卡尔算法两 种。

最短路径问题是图的一个比较典型的应用问题。短路径是网中求一个顶点 到另一个顶点的所有路径中边的权值之和小的路径。可以求从一个顶点到网中 其余顶点的短路径,这称之为单源点问题,也可以求网中任意两个顶点之间的 短路径。本章只讨论了单源点问题。解决单源点问题的算法是狄克斯特拉算法。
 拓扑排序是图中重要的运算之一,在实际中应用很广泛。AOV 网是顶点之 间存在优先关系的有向图。拓扑排序是解决 AOV 网中是否存在环路的有效手段, 若拓扑有序序列中包含 AOV 网中所有的顶点,则 AOV 网中不存在环路,否则 存在环路

 

以上内容 摘自数据结构C#语言描述  此书市面上买不到了只有电子版. 留在博客上以供参考

 

你可能感兴趣的:(数据结构与算法)