Created Date : 2020-01-31 14:21:43Last Upgraded Date : 2020-02-07 14:51:27
树和森林 树和森林的实现 树的存储结构
双亲表示法 Data域 存储结点的数据信息 Parent域 存储结点的双亲在数组中的序号。 实现求双亲操作很方便,但对于求某节点的孩子节点的操作需要查询整个数组,实现求兄弟的操作也比较困难。
孩子表示法
多重链表 链表中的每个结点包括一个数据域和多个指针域。数据域存储树中结点的自身信息,每个指针指向该结点的一个孩子结点。
数组+单链表 一维数组顺序存储树中各节点的信息,并将各结点的孩子信息组成一个单链表。
双亲 - 孩子表示法 将各结点的孩子结点组成一个单链表,同时用一维数组顺序存储树中的各节点,数组元素包括结点的自身信息、双亲结点在数组中的序号以及该结点的孩子结点链表的头指针。
孩子 - 兄弟表示法 firstchild data nextsibling 二重链表表示法 查找某结点的孩子结点比较方便,如果在每一个结点中增加一个指向双亲的指针,就可以方便地找到各结点的祖先。
树、森林和二叉树的转换
树转化为二叉树 把树当作有序树看待:约定树中每一个结点的孩子结点按从左到右的次序顺序编号。 连线 :树中所有相邻兄弟结点之间加一条线。 删线 :对树中的每一个结点,只保留它与第一个孩子结点之间的连线,删去它与其他孩子结点之间的连线。 美化 :以树的根结点为轴心,将这棵树顺时针转动45度使其层次分明。 树作这样的转化所构成的二叉树是唯一的。 树转化成的二叉树,其根结点无右子树。
森林转化为二叉树 依次将森林中每棵树转化成相应的二叉树。 从第二棵二叉树开始,依次把当前的二叉树作为前一棵二叉树根结点的右子树,此时所得到的二叉树就是由森林转化得到的二叉树。 森林转化成的二叉树,其根结点有右子树。
二叉树转化为森林 连线 :若结点p是其双亲结点F的 左孩子,则把从结点p延沿右分支所找到的所有结点和结点F用线连起来。 删线 :删除二叉树中所有结点和其右孩子结点之间的连线。 美化 :整理,使结构层次分明。
树的遍历 指按照某种顺序访问树中的每个结点,并使每个结点被访问一次且只被访问一次。
树的先根遍历 若树为空,遍历结束。否则, 访问根结点; 按照从左到右的顺序先根遍历根结点的每一棵子树。 与二叉树先序遍历结果序列相同。
树的后根遍历 若树为空,遍历结束。否则, 按照从左到右的顺序后根遍历根结点的每一棵子树; 访问根结点。 与二叉树中序遍历结果序列相同。
树的层次遍历 又称为树的广度遍历。从树的第一层(根结点)开始,自上至下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。 借助队列,结构按下述步骤层序遍历树: 1)初始化队列,并将根结点入队。 2)当队列非空时,取出队头结点p,转步骤3);如果队列为空,则结束遍历。 3)访问取出的结点p;如果结点p有孩子,则依次将它们入队列。 4)重复步骤2)、3),直到队列为空。
森林的遍历
森林的先根遍历 若森林为空,返回;否则, 访问森林中第一棵树的根结点; 先根遍历第一棵树的根结点的子树森林; 先根遍历除第一棵外其他树组成的森林。 与二叉树先序遍历结果序列相同。
森林的中根遍历 若森林为空,返回;否则, 中根遍历第一棵树的根结点的子树森林; 访问森林中第一棵树的根结点; 中根遍历除第一棵外其他树组成的森林。 与二叉树中序遍历结果序列相同。
森林的后根遍历 若森林为空,返回;否则, 后根遍历第一棵树的根结点的子树森林; 后根遍历除第一棵外其他树组成的森林; 访问森林中第一棵树的根结点。 与二叉树后序遍历结果序列相同。
等价类及其表示 等价关系与等价类 先把每一个对象看作是一个单元素集合,然后依次将一个等价对中两个元素所在的集合合并。
并查集 指能够完成查找、合并功能的集合。 支持以下三种操作:
Ufsets(n):构造函数,将并查集中n个元素初始化为n个只有一个单元素的子集合。
Union(S1,S2):把集合S2并入集合S1中。要求S1与S2互不相交,否则没有结果。
Find(d):查找单元素d所在的集合,并返回该集合的名字。
等价类问题的解法 对于并查集来说,每个子集(等价类)用一棵树表示,子集合中每个元素用树中的一个结点表示。用树的根结点来代表相应的等价类集合。 等价类树用双亲表示法表示(当然根据需要可以建立集合名字表示该集合的树的根结点之间的对应关系);此外,树的根结点的双亲域的值设为-k(parent=-k),其中k为该树中的结点数(即所代表等价类中的元素数目)。 对于任意给定的集合元素D,只要通过双亲指针向上一直走到树的根结点,就可以得到元素D所在的等价类(用根结点代表相应的等价类)。对于两个集合的并,只要将其中一个集合的根结点设置为另一个集合的根结点的孩子即可。
性能改进 ——避免产生退化的树Union(i,j)的加权规则WeightedUnion() :合并两个集合时,先判断两集合中元素的个数,如果以i为根的树中的结点个数少于以j为根的树中的结点个数,则让j成为i的双亲;否则,让i成为j的双亲。 执行一次WeightedUnion()比执行一次Union()时间要多,但仍在常量界限O(1)范围内。查找操作Find()保持不变,其查找时间的上界不超过树的高度加1。
优化查找的折叠规则 :设j是以i为根的树中的一个结点,则对于从j到根i的路径上的每一个结点k,如果k的双亲不等于i,则把i设置为k的双亲。 使用折叠规则完成一次查找,所需时间比Find()多,但是能改善树的性能,减少以后查找操作所需的时间。
图 图的基本概念 图的定义 无向图 undirected graph 有向图 directed graph 以下讨论限制于:图中不能有从顶点到自身的环(即自身环);两个顶点v和w之间相关联的边不能多于一条。
图的术语
完全图 complete graph
权 weight
带权图=网络 network
邻接点 adjacent vertex
子图 subgraph
顶点的度 degree
路径 path
路径长度 path length
简单路径与回路 cycle
连通图与连通分量 connected graph and connected component
强连通图与强连通分量 strongly connected digraph
生成树 spanning tree
图的基本操作
InsertVertex(d)
InsertEdge(v1,v2)
DeleteVertex(d)
DeleteEdge(v1,v2)
GetWeight(v1,v2)
GetFirstNeighbor(int v)
GetNextNeighbor(int v1,int v2)
Travers()
IsEmpty()
图的存储结构 邻接矩阵
顶点信息 —— 顶点表 顶点关系 —— 邻接矩阵
N个定点的图其邻接矩阵是一个二维数组arcs[N][N],定义如下。 [image:705B5107-1A6B-4C3A-AF6A-BE39846E0496-16898-0000936F10705A3B/D67FBC64-A83D-4414-B986-F6E0D1787DE3.png] 无向图的邻接矩阵一定是对称的,将第i行的元素值或第i列的元素值累加起来就得到顶点i的度。 有向图的邻接矩阵不一定对称,将第j列的所有元素值累加起来就得到顶点Vj的入度ID(Vj);将第i行的所有元素值累加起来就得到顶点Vi的出度OD(Vi)。 对于网络(或带权图)邻接矩阵定义如下。 [image:C8816614-D7FC-4C3D-AB38-07F1F7D967D9-16898-00009392C7625CB8/56365ACE-F02E-4A11-98CA-FFCADDEF32AF.png] 对于带权有向图,第i行中所有非零且不等于∞的元素数目就是顶点i的出度;第j列中所有非零且不等于∞的元素数目就是顶点j的入度。
无向图邻接矩阵类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 template <class ElemType >class AdjMatrixUndirGraph { protected : int vexNum, vexMaxNum, arcNum; int **arcs; ElemType *vertexes; mutable Status *tag; public : AdjMatrixUndirGraph(ElemType es[], int vertexNum, int vertexMaxNum = DEFAULT_SIZE); AdjMatrixUndirGraph(int vertexMaxNum = DEFAULT_SIZE); ~AdjMatrixUndirGraph(); void Clear () ; bool IsEmpty () ; int GetOrder (ElemType &d) const ; Status GetElem (int v, ElemType &d) const ; Status SetElem (int v, const ElemType &d) ; int GetVexNum () const ; int GetArcNum () const ; int FirstAdjVex (int v) const ; int NextAdjVex (int v1, int v2) const ; void InsertVex (const ElemType &d) ; void InsertArc (int v1, int v2) ; void DeleteVex (const ElemType &d) ; void DeleteArc (int v1, int v2) ; Status GetTag (int v) const ; void SetTag (int v, Status val) const ; AdjMatrixUndirGraph(const AdjMatrixUndirGraph<ElemType> &g); AdjMatrixUndirGraph<ElemType> &operator =(const AdjMatrixUndirGraph<ElemType> &g); void Display () ; };
邻接表 是邻接矩阵的改进,把邻接矩阵的每一行改为一个单链表。 顶点结点:表示每一个顶点。其中保存着该顶点的嘻嘻和指向该结点相应的边(弧)链表的指针。用顺序表存储,假设顶点的序号为数组的下标。 对于无向图 : 边链表:依附于同一个顶点的边链接在同一个单链表中。 边结点:边链表中的每一个结点代表一条边。其中保存着与该边相关联的另一个顶点的序号和指向同一链表中下一个边结点的指针。 对于有向图 : 弧链表:从同一个顶点发出的弧链接在同一个单链表中。 弧结点:弧链表中的每一个结点代表一条弧。其中保存着该弧的弧头顶点序号和指向同一链表中下一个弧结点的指针。 对于带权图 : 边(弧)结点中另保存该边(弧)上的权值。
邻接表的边(弧)链表中,各个边(弧)结点的链入顺序是任意的,可根据边结点输入次序而定。 设图有n个顶点和e条边:用邻接表表示无向图时,需要n个顶点结点和2e个边结点;用邻接表表示有向图时,需要n个顶点结点和e个边结点(不考虑逆邻接表)。当e很小时,可以大量节省存储空间。 此外,把关联于同一个顶点的所有边(弧)链接在一个单链表中,可以大大方便图的操作。
有向网邻接表顶点结点类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <class ElemType , class WeightType >struct AdjListNetWorkVex { ElemType data; AdjListNetworkArc<WeightType> *firstarc; AdjListNetWorkVex(); AdjListNetWorkVex(ElemType val, AdjListNetworkArc<WeightType> *adj = NULL ); };
有向网邻接表弧结点类模板
1 2 3 4 5 6 7 8 9 10 11 12 template <class WeightType >struct AdjListNetworkArc { int adjVex; WeightType weight; AdjListNetworkArc<WeightType> *nextarc; AdjListNetworkArc(); AdjListNetworkArc(int v, WeightType w, AdjListNetworkArc<WeightType> * next = NULL ); };
有向网邻接表类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 template <class ElemType , class WeightType >class AdjListDirNetwork { protected : int vexNum, vexMaxNum, arcNum; AdjListNetWorkVex<ElemType, WeightType> *vexTable; mutable Status *tag; WeightType infinity; public : AdjListDirNetwork(ElemType es[], int vertexNum, int vertexMaxNum = DEFAULT_SIZE, WeightType infinit = (WeightType)DEFAULT_INFINITY); AdjListDirNetwork(int vertexMaxNum = DEFAULT_SIZE, WeightType infinit = (WeightType)DEFAULT_INFINITY); ~AdjListDirNetwork(); void Clear () ; bool IsEmpty () ; int GetOrder (ElemType &d) const ; Status GetElem (int v, ElemType &d) const ; Status SetElem (int v, const ElemType &d) ; WeightType GetInfinity () const ; int GetVexNum () const ; int GetArcNum () const ; int FirstAdjVex (int v) const ; int NextAdjVex (int v1, int v2) const ; void InsertVex (const ElemType &d) ; void InsertArc (int v1, int v2, WeightType w) ; void DeleteVex (const ElemType &d) ; void DeleteArc (int v1, int v2) ; WeightType GetWeight (int v1, int v2) const ; void SetWeight (int v1, int v2, WeightType w) ; Status GetTag (int v) const ; void SetTag (int v, Status tag) const ; AdjListDirNetwork(const AdjListDirNetwork<ElemType, WeightType> ©); AdjListDirNetwork<ElemType, WeightType> &operator = (const AdjListDirNetwork<ElemType, WeightType> ©); void Display () ; };
邻接多重表 图的每一条边用一个边结点表示,由六个域组成。 标记域 tag 标记该边是否被处理过或搜索过 信息域 weight 存储边的权值 顶点域 adjvex1 adjvex2 该边所依附的两个顶点在图中的序号 链接指针 nextarc1 nextarc2 指向下一条依附于顶点adjvex1/adjvex2的边 顶点结点 包含data域存储有关顶点的信息、firstarc域指向第一条依附于该顶点的边 == 所有的顶点结点组成一个顺序表
十字链表 图的每一条边用一个弧结点表示,由六个域组成。 标记域 tag 标记该边是否被处理过或搜索过 信息域 weight 存储边的权值 顶点域 tailvex headvex 弧尾顶点序号和弧头顶点序号 链接指针 tailnextarc headnextarc 指向下一条以顶点tailvex为始点(弧尾)的弧和下一条以顶点headvex为终点(弧头)的弧 顶点结点 包含data域存储有关顶点的信息、firstinarc域指向第一条以该顶点为终点的弧、firstoutarc域指向第一条以该顶点为始点的弧 == 所有的顶点结点组成一个顺序表
图的遍历与连通性 对于给定的图,沿着一些边(弧)访问图中所有的顶点,且使每个顶点仅被访问一次。
深度优先遍历 深度优先搜索 1)访问结点v,并标记v已被访问。 2)取顶点v的第一个邻接顶点w。 3)若顶点w不存在,算法结束;否则继续步骤4)。 4)若顶点w未被访问,则访问结点w,并标记w已被访问。 5)使w为顶点v的在原来w之后的下一个邻接顶点,转到步骤3)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 template <class ElemType >void DFSTraverse (const AdjMatrixUndirGraph <ElemType> &g , void (*Visit )(const ElemType &))// 初始条件:存在图g // 操作结果:对无向图g 进行深度优先遍历 { int v; for (v = 0 ; v < g.GetVexNum(); v++) g.SetTag(v, UNVISITED); for (v = 0 ; v < g.GetVexNum(); v++) if (g.GetTag(v) == UNVISITED) DFS(g, v , Visit); } template <class ElemType >void DFS (const AdjMatrixUndirGraph <ElemType> &g , int v , void (*Visit )(const ElemType &))// 初始条件:存在图g // 操作结果:从顶点v 出发进行深度优先搜索 { ElemType e; g.SetTag(v, VISITED); g.GetElem(v, e); Visit(e); for (int w = g.FirstAdjVex(v); w != -1 ; w = g.NextAdjVex(v, w)) if (g.GetTag(w) == UNVISITED) DFS(g, w , Visit); }
图中n个顶点,e条边。 存储结构为邻接表,时间复杂度为O(n+e)。 存储结构为邻接矩阵,时间复杂度为O(n^2)。
广度优先遍历 广度优先搜索 1)访问结点v,并标记v已被访问,同时顶点v入队列。 2)当队列空时算法结束,否则继续步骤3)。 3)队头顶点出队列为v。 4)取顶点v的第一个邻接顶点w。 5)若顶点w不存在,转步骤3);否则继续步骤6)。 6)若顶点w未被访问,则访问结点w,并标记w已被访问,同时顶点w入队列;否则继续步骤7)。 7)使w为顶点v的在原来w之后的下一个邻接顶点,转到步骤5)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 template <class ElemType >void BFSTraverse (const AdjMatrixUndirGraph <ElemType> &g , void (*Visit )(const ElemType &))// 初始条件:存在图g // 操作结果:对图g 进行广度优先遍历 { int v; for (v = 0 ; v < g.GetVexNum(); v++) g.SetTag(v, UNVISITED); for (v = 0 ; v < g.GetVexNum(); v++) if (g.GetTag(v) == UNVISITED) BFS(g, v , Visit); } template <class ElemType >void BFS (const AdjMatrixUndirGraph <ElemType> &g , int v , void (*Visit )(const ElemType &))// 初始条件:存在图g // 操作结果:从顶点v 出发进行广度优先搜索 { LinkQueue<int > q; int u, w; ElemType e; g.SetTag(v, VISITED); g.GetElem(v, e); Visit(e); q.EnQueue(v); while (!q.IsEmpty()) { q.DelQueue(u); for (w = g.FirstAdjVex(u); w != -1 ; w = g.NextAdjVex(u, w)) if (g.GetTag(w) == UNVISITED){ g.SetTag(w, VISITED); g.GetElem(w, e); Visit(e); q.EnQueue(w); } } }
图中n个顶点,e条边。 存储结构为邻接表,时间复杂度为O(n+e)。 存储结构为邻接矩阵,时间复杂度为O(n^2)。
连通分量 非连通图有k个连通分量,就要k次调用DFS或BFS才能访问图中的所有顶点。 深度优先生成森林、广度优先生成森林。
最小生成树 Minimum-cost spanning tree 克鲁斯卡尔算法 Kruskal 避圈法 1)初始化,在并查集中,连通网络的每一个顶点独立成一个等价类,连通网络的所有的边建立最小堆,最小生成树T中没有任何边,T中边的条数计数器i为0。 2)如果T中边的条数计数器i等于顶点数减1,则算法结束;否则继续步骤3)。 3)选取堆顶元素代表的边(v,u),同时调整堆。 4)利用并查集的运算检查依附于边(v,u)的两个顶点v和u是否在同一个连通分量(即并查集的同一个子集合)上,如果是则转步骤2);否则继续步骤5)。 5)将边(v,u)加入到最小生成树T中,同时将这两个顶点所在的连通分量合并成一个连通分量(即并查集中的相应两个子集合并成一个子集),继续步骤2)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 template <class ElemType , class WeightType >void MiniSpanTreeKruskal (const AdjMatrixUndirNetwork <ElemType, WeightType> &g )// 初始条件:存在网g // 操作结果:用Kruskal 算法构造网g 的最小代价生成树 { int count, VexNum = g.GetVexNum(); KruskalEdge<ElemType, WeightType> KEdge; MinHeap<KruskalEdge<ElemType, WeightType> > ha(g.GetEdgeNum()); ElemType *kVex, v1, v2; kVex = new ElemType[VexNum]; for (int i = 0 ; i < VexNum; i++) g.GetElem(i, kVex[i]); UFSets<ElemType> f (kVex,VexNum) ; for (int v = 0 ; v < g.GetVexNum(); v++) for (int u = g.FirstAdjVex(v); u >= 0 ; u = g.NextAdjVex(v, u)) if (v < u) { g.GetElem(v, v1); g.GetElem(u, v2); KEdge.vertex1 = v1; KEdge.vertex2 = v2; KEdge.weight = g.GetWeight(v,u); ha.Insert(KEdge); } count = 0 ; while (count < VexNum - 1 ) { ha.DeleteTop(KEdge); v1 = KEdge.vertex1; v2 = KEdge.vertex2; if (f.Differ(v1, v2)) { cout << "边:( " << v1 << ", " << v2 << " ) 权:" << KEdge.weight << endl ; f.Union(v1, v2); count++; } } }
连通网络中n个顶点,e条边。 初始建堆,需要e次调用堆的插入操作,时间复杂度为O(elog2e)。 构造最小生成树,最多调用e次堆的删除操作、2e次并查集的查找操作和n-1次并查集的合并操作,时间复杂度分别为O(elog2e)、O(elog2e)和O(n)。 总时间复杂度O(elog2e)。 Kruskal算法适用于稀疏的(顶点个数较多而边数较少)连通网络。
普里姆算法 Prim 1)输入:一个加权连通图,其中顶点集合为V,边集合为E; 2)初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空; 3)重复下列操作,直到Vnew = V: a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一); b.将v加入集合Vnew中,将<u, v>边加入集合Enew中; 4)输出:使用集合Vnew和Enew来描述所得到的最小生成树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 template <class ElemType , class WeightType >void MiniSpanTreePrim (const AdjMatrixUndirNetwork <ElemType, WeightType> &g , int u0 )// 初始条件:存在网g ,u0 为g 的一个顶点 // 操作结果:用Prim 算法从u0 出发构造网g 的最小代价生成树 { WeightType min; ElemType v1, v2; int vexnum = g.GetVexNum(); CloseArcType<ElemType, WeightType> * closearc; if (u0 < 0 || u0 >= vexnum) throw Error("顶点u0不存在!" ); int u, v, k; closearc = new CloseArcType<ElemType, WeightType>[vexnum]; for (v = 0 ; v < vexnum; v++) { closearc[v].nearvertex = u0; closearc[v].lowweight = g.GetWeight(u0, v); } closearc[u0].nearvertex = -1 ; closearc[u0].lowweight = 0 ; for (k = 1 ; k < vexnum; k++) { min = g.GetInfinity(); v = u0; for (u = 0 ; u < vexnum; u++) if (closearc[u].lowweight != 0 && closearc[u].lowweight < min) { v = u; min = closearc[u].lowweight; } if (v != u0) { g.GetElem(closearc[v].nearvertex, v1); g.GetElem(v, v2); cout << "边:( " << v1 << ", " << v2 << " ) 权:" << min << endl ; closearc[v].lowweight = 0 ; for (u = g.FirstAdjVex(v); u != -1 ; u = g.NextAdjVex(v, u)) if (closearc[u].lowweight != 0 && (g.GetWeight(v, u) < closearc[u].lowweight)) { closearc[u].lowweight = g.GetWeight(v, u); closearc[u].nearvertex = v; } } } delete []closearc; }
连通网络中n个顶点,e条边。 辅助数组closearc[]初始化,n-1次for循环,时间复杂度为O(n)。 把n-1个顶点加入U中,对每加入一个顶点到U,有两个并列的for循环分别实现查找具有最小权值的边和修改辅助数组closearc[],时间复杂度均为O(n)。 总时间复杂度O(n^2)。 Prim算法适用于边稠密的连通网络。
最短路径 弧上权值为非负情形的单源点最短路径问题 —— Dijkstra 根据初始点,挨个的把离初始点最近的点一个一个找到并加入集合,集合中所有的点的d[i]都是该点到初始点最短路径长度,由于后加入的点是根据集合S中的点为基础拓展的,所以也能找到最短路径。 1)清除所有点的标号; 2)设d[0]=0,其他d[i]=INF; //INF是一个很大的值,用来替代正无穷 3)循环n次 { 在所有未标号结点中,选出d值最小的结点x; 给结点x标记; 对于从x出发的所有边(x,y),更新d[y] = min{d[y], d[x]+w(x,y) }
简单易懂——Dijkstra算法讲解_Machine Learning with Turing’s Cat-CSDN博客 数据结构(十二):最短路径(Dijkstra算法) - 简书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 template <class ElemType , class WeightType >void ShortestPathDij (const AdjListDirNetwork <ElemType, WeightType> &g , int v0 , int *path , WeightType *dist ) // 操作结果: 用Dijkstra算法求有向网g从顶点v0到其余顶点v的最短路径path和路径长度dist[v],{ WeightType minVal, infinity = g.GetInfinity(); int v, u; for (v = 0 ; v < g.GetVexNum(); v++){ dist[v] = (v0 != v) ? g.GetWeight(v0, v) : 0 ; if (dist[v] == infinity) path[v] = -1 ; else path[v] = v0; g.SetTag(v, UNVISITED); } g.SetTag(v0, VISITED); for (int i = 1 ; i < g.GetVexNum(); i++){ minVal = infinity; u = v0; for (v = 0 ; v < g.GetVexNum(); v++) if (g.GetTag(v) == UNVISITED && dist[v] < minVal) { u = v; minVal = dist[v]; } g.SetTag(u, VISITED); for (v = g.FirstAdjVex(u); v != -1 ; v = g.NextAdjVex(u, v)) if (g.GetTag(v) == UNVISITED && minVal + g.GetWeight(u, v) < dist[v]) { dist[v] = minVal + g.GetWeight(u, v); path[v] = u; } } }
弧上权值为任意值的单源点最短路径问题(自学)—— BellmanFord
数据结构(十一):最短路径(Bellman-Ford算法) - 简书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 template <class ElemType , class WeightType >void ShortestPathBellmanFord (const AdjListDirNetwork <ElemType, WeightType> &g , int v0 , int *path , WeightType *dist ) // 操作结果: 用BellmanFord算法求有向网g从顶点v0到其余顶点v的最短路径path和路径长度dist[v],{ WeightType *distTemp, minVal, infinity = g.GetInfinity(); int v, u, vexNum = g.GetVexNum(); distTemp = new WeightType[vexNum]; for (v = 0 ; v < vexNum; v++){ dist[v] = (v0 != v) ? g.GetWeight(v0, v) : 0 ; if (dist[v] == infinity) path[v] = -1 ; else path[v] = v0; } for ( int k = 2 ; k < vexNum ; k++) { for (v = 0 ; v < vexNum; v++) distTemp[v] = dist[v]; for (u = 0 ; u < vexNum ; u++) if (u != v0) for (v = 0 ; v < vexNum; v++) if (v != v0 && distTemp[u] > dist[v] + g.GetWeight(v, u)) { distTemp[u]= dist[v] + g.GetWeight(v, u); path[u] = v; } for (v = 0 ; v < vexNum; v++) dist[v] = distTemp[v]; } }
所有顶点之间的最短路径 —— Floyd 通过一个图的权值矩阵 求出它的每两点间的最短路径矩阵。 从图的带权邻接矩阵A=[a(i,j)] n×n开始,递归地进行n次更新,即由矩阵D(0)=A,按一个公式,构造出矩阵D(1);又用同样地公式由D(1)构造出D(2);……;最后又用同样的公式由D(n-1)构造出矩阵D(n)。矩阵D(n)的i行j列元素便是i号顶点到j号顶点的最短路径长度,称D(n)为图的距离矩阵 ,同时还可引入一个后继节点矩阵path来记录两点间的最短路径。 采用松弛技术(松弛操作),对在i和j之间的所有其他点进行一次松弛。所以时间复杂度为O(n^3); 空间复杂度为O(n^2)。
数据结构(十三):最短路径(Floyd算法) - 简书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 template <class ElemType , class WeightType >void ShortestPathFloyd (const AdjListDirNetwork <ElemType, WeightType> &g , int **path , WeightType **dist ) // 操作结果: 用Floyd算法求有向网g中各对顶点u和v之间的最短路径path[u][v]和路径长度{ for (int u = 0 ; u < g.GetVexNum(); u++) for (int v = 0 ; v < g.GetVexNum(); v++) { dist[u][v] = (u != v) ? g.GetWeight(u, v) : 0 ; if (u != v && dist[u][v] < g.GetInfinity()) path[u][v] = u; else path[u][v] = -1 ; } for (int k = 0 ; k < g.GetVexNum(); k++) for (int i = 0 ; i < g.GetVexNum(); i++) for (int j = 0 ; j < g.GetVexNum(); j++) if (dist[i][k] + dist[k][j] < dist[i][j]) { dist[i][j] = dist[i][k] + dist[k][j]; path[i][j] = path[k][j]; } }
活动网络 用顶点表示活动的网络 AOV —— 拓扑排序 Activity On Vertex AOV网络中不能出现有向回路(有向环)。 对AOV网络构造它的拓扑有序序列,即将AOV网络中的各个顶点排列成一个线性有序的序列,使得AOV网络中所有的前驱和后继关系都能得到满足。 拓扑排序 <==> 没有有向环 一个AOV网络的拓扑有序序列不是唯一的。
拓扑排序简单方法 1)在AOV网络中选一个没有直接前驱的顶点v;并输出之。 2)从图中删去该顶点,同时删去所有从顶点v发出的弧。 3)重复以上步骤1)和2),直到没有直接前驱的顶点全部输出。如果图中所有顶点已全部输出,则拓扑有序序列形成,拓扑排序完成;否则说明图中还剩下一些顶点,它们都有直接前驱,再也找不到没有前驱的顶点了,这时AOV网络必定存在有向环。
使用栈(或队列)的拓扑排序算法 1)建立入度为零的顶点栈。 2)当入度为零的顶点栈为空时算法转步骤6),否则继续步骤3)。 3)入度为零的顶点栈中栈顶元素v出栈,并输出之顶点v。 4)从AOV网络中删去顶点v和多有从顶点v发出的弧<v,j>,并将顶点j的入度减1。 5)如果顶点j入度减至0,则将该顶点进入入度为零的顶点栈;转步骤2)。 6)如果输出顶点个数少于AOV网络的顶点个数,则输出网络中存在有向环的信息;算法结束。为了建立入度为零的顶点栈,可以不另外分配存储空间,直接利用顶点的入度数组InDegree[]中入度为零的元素,建立入度为零的静态链栈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 template <class ElemType >Status TopSort (const AdjListDirGraph <ElemType> &g )// 初始条件:存在有向图g // 操作结果:如g 无回路,则输出g 的顶点的一个拓扑序列,并返回SUCCESS ,否则返回FAIL { int *indegree = new int [g.GetVexNum()]; int v, u, count = 0 , top = -1 ; ElemType e; StatIndegree(g, indegree); for (v = 0 ; v < g.GetVexNum(); v++) if (indegree[v] == 0 ) { indegree[v] = top; top = v; } while (top != -1 ) { v = top; top = indegree[v]; g.GetElem(v, e); cout << e << " " ; count++; for (u = g.FirstAdjVex(v); u != -1 ; u = g.NextAdjVex(v, u)) if (--indegree[u] == 0 ) { indegree[u] = top; top = u; } } delete []indegree; if (count < g.GetVexNum()) return FAIL; else return SUCCESS; }
AOV网络中n个顶点,e条边。 搜索入度为零的顶点建立链栈,for循环,时间复杂度为O(n)。 (网络中没有回路)所有顶点都需要进一次栈,出一次栈,时间复杂度为O(n)。 顶点入度减1的运算,执行e次,时间复杂度O(e)。 总时间复杂度O(n+e)。
用边表示活动的网络 AOE —— 关键路径 Activity On Edges AOE网络中不能出现有向回路(有向环)。 AOE网络中不能出现多个入度为0的顶点。 AOE网络中只有一个入度为0的顶点(称为源点)用于彼岸是这个开始事件;只有一个出度为0的顶点(称为汇点)用于表示这个完成事件。由于活动有先后或并行,从源点到汇点的路径可能不止一条,相应路径上所有活动的持续时间之和可能是不同的,但只有当各条路径上的所有活动都完成了,整个工程才算完成。整个工程完成所需的最短时间取决于从源点到汇点的最长路径长度 ,这条路径最长的路径就叫做关键路径 (critical path),关键路径上的所有活动都是关键活动 (即不按期完成就会影响整个工程完成的活动)。 定义以下相关量找出关键活动: 1)事件Vi的最早可能发生时间Ve[i](V early) 2)事件Vi的最迟允许发生时间Vl[i](V late) 3)活动ak的最早可能开始时间e[k] 4)活动ak的最迟允许开始时间l[k] 5)活动ak的时间余量l[k]-e[k](时间余量为零的活动室关键活动)
计算关键路径的算法步骤 1)输入n个顶点和e条带权的有向边,建立邻接表结构。 2)从源点V0出发,令Ve[0]=0,按拓扑有序的顺序计算每个顶点的Ve[j](j=1,2,…,n-1)。若拓扑排序中遍历的顶点数小于n,则说明网络中存在有向环,不能继续求关键路径。 3)从汇点Vn-1出发,令Vl[n-1]=Ve[n-1],按拓扑有序顺序求各顶点的Vl[i](i=0,1,…,n-2)。 4)根据各顶点Vi和Ve[i]和Vl[i]的值,求各条弧ak的e[k]和l[k]。 5)输出关键活动ak(e[k]==l[k]即为关键活动)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 template <class ElemType , class WeightType >Status CriticalPath (const AdjListDirNetwork <ElemType, WeightType> &g )// 初始条件:存在有向网g // 操作结果:如g 无回路,则输出g 的关键活动,并返回SUCCESS ,否则返回FAIL { int *indegree = new int [g.GetVexNum()]; WeightType *ve = new int [g.GetVexNum()]; WeightType *vl = new int [g.GetVexNum()]; LinkQueue<int > q; LinkStack<int > s; int ee, el, u, v, count = 0 ; ElemType e1, e2; for (v = 0 ; v < g.GetVexNum(); v++) ve[v] = 0 ; StatIndegree(g, indegree); for (v = 0 ; v < g.GetVexNum(); v++) if (indegree[v] == 0 ) q.EnQueue(v); while (!q.IsEmpty()) { q.DelQueue(u); s.Push(u); count++; for (v = g.FirstAdjVex(u); v != -1 ; v = g.NextAdjVex(u, v)) { if (--indegree[v] == 0 ) q.EnQueue(v); if (ve[u] + g.GetWeight(u, v) > ve[v]) ve[v] = ve[u] + g.GetWeight(u, v); } } delete []indegree; if (count < g.GetVexNum()) { delete []ve; return FAIL; } s.Top(u); for (v = 0 ; v < g.GetVexNum(); v++) vl[v] = ve[u]; while (!s.IsEmpty()) { s.Pop(u); for (v = g.FirstAdjVex(u); v != -1 ; v = g.NextAdjVex(u, v)) if (vl[v] - g.GetWeight(u, v) < vl[u]) vl[u] = vl[v] - g.GetWeight(u, v); } for (u = 0 ; u < g.GetVexNum(); u++) { for (v = g.FirstAdjVex(u); v != -1 ; v = g.NextAdjVex(u, v)) { ee = ve[u]; el = vl[v] - g.GetWeight(u, v); if (ee == el) { g.GetElem(u, e1); g.GetElem(v, e2); cout << "<" << e1 << "," << e2 << "> " ; } } } delete []ve; delete []vl; return SUCCESS; }
AOE网络中n个顶点,e条边。 求各顶点的Ve[i]和Vl[i],时间复杂度为O(n+e)。 求各个活动的e[k]和l[k],时间复杂度为O(n+e)。 总时间复杂度O(n+e)。不是任一关键活动加速就一定能使整个工程提前完成。 在存在多条关键路径的情况下,只有加快那些处在所有关键路径上的公共关键活动的进度,才能提前完成整个工程。而这些关键活动的加速是有限度的,即不能改变关键路径。