《数据结构》学习笔记(6)——图

本文围绕图展开,介绍了图的基本概念,包括定义、分类、顶点和边的关系等。阐述了图的存储结构,如邻接矩阵、邻接表等。还讲解了图的遍历方法,如深度遍历和广度优先遍历。此外,介绍了最小生成树、最短路径、拓扑排序和关键路径等相关算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:基本概念

1. 定义

图是由顶点的有穷非空集合和顶点之间边的集合组成。

2. 表示:G (V,E)

其中,G表示一个图,V是图G中顶点的集合(有穷非空),E是图G中边的集合。

3. 分类

按方向分类:
  • 无向边:若顶点 vi 到 vj 之间的边没有方向,则称这条边为无向边,用无序偶对(vi,vj)来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。
  • 有向边:若从顶点 vi 到 vj 之间的边有方向,则称这条边为有向边,也称为弧,用有序偶<vi,vj>来表示。vi为弧尾,vj为弧头。如果图中任意两个顶点之间的边都是有向边,则称该图为有向图。
按是否复杂分类:
  • 无向完全图:在无向图中,任意两个顶点之间都存在边(含有n个顶点的无向完全图有[n×(n-1)]/2条边)

  • 有向完全图:在有向图中,任意两个顶点之间都存在方向互为相反的两条弧(含有n个顶点的有向完全图有nx (n-1)条边)

  • 多重图:含有平行边的图,也就是同一条边重复出现。

  • 简单图:在图中,不存在顶点到其自身的边,且同一条边不重复出现。(即没有环和平行边)

其他
  • 权:图的边或弧有与它相关的数字。这些权可以表示从一个顶点到另一个顶点的距离或耗费。带权的图通常称为网。
  • 子图:假设有两个图G1= (V1,{E1})和G2 = (V2,{E2}),如果V2包含于V1中且E2包含于E1中,则称G2为G1的子图。

4. 图的顶点和边的关系

(1)对于无向图 G = ( V {E}), 如果边(v1,v2) € E:
  • 邻接:, 顶点v1和v2互为邻接点,即 v1和v2相邻接。
  • 关联:边(v1,v2)依附于顶点v1和v2,或者说(v1,v2)与顶点v1和v2相关联。

顶点v的度是和v相关联的边的数目,记为TD(v) 。图的边数就是各顶点度数和的一半。

(2)对于有向图G=(V {E} ),如果弧<v1,v2>€E:
  • 邻接:顶点v1和v2互为邻接点,即 v1和v2相邻接。顶点V邻接自顶点V。
  • 关联:弧<v1,v2>和顶点v1,v2相关联。

以顶点v为头的弧的数目称为v的入度,记为ID(v)
以顶点v为尾的弧的数目称为v的出度,记为OD(v)
顶点v的度TD(v)=ID(v)+OD(v) 。图的边数就是各顶点出度和或入度和。

5. 图的密度:

密度的定义:已连接的顶点对占可能连接的顶点对的比例

  • 稀疏图:有很少条边或弧的图,密度小
  • 稠密图:有很多条边或弧的图,密度大

6. 图的可达性

路径的定义:

  • 无向图G= (V;{E})中从顶点v1到顶点v2的路径是一个顶点序列(v1=vi,0,vi,1… vij = v2),其中(vi,j-1 vi,j)€E
  • 有向图G= (V;{E})中从顶点v1到顶点v2的路径是一个顶点序列(v1=vi,0,vi,1… vij = v2),其中 <vi,j-1 vi,j> €E

路径的数量:树中根结点到任意结点的路径是唯一的,但是图中顶点与顶点之间的路径却不唯一。

路径的长度:路径上的边或弧的数目。

特殊的路径:

  • 简单路径:序列中顶点不重复出现的路径。
  • 回路:第一个顶点到最后一个顶点相同的路径称为回路或环。
  • 简单回路或简单环:除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路。

7. 图的连通性

图:

  • 连通图:在无向图G中,如果从顶点v1到顶点v2有路径,则称v1和v2是连通的。如果对于图中任意两个顶点 vi, vj € E, vi 和 vj 都是连通的,则称G是连通图。
  • 强连通图:在有向图G中,如果对于每一对vi、vj€ V、Vi != Vj,从vi到vj和从vj到vi都存在路径,则称G是强连通图。

分量:非连通图的若干个连通部分

  • 连通分量:无向图中的极大连通子图称为连通分量。(连通图的连通分量只有一个,即是其自身,非连通的无向图有多个连通分量)
  • 强连通分量:有向图中的极大强连通子图称做强连通分量。(强连通图只有一个强连通分量,即是其自身,非强连通的有向图有多个强连分量)

连通分量的特点:

  • 是子图;
  • 是连通图;
  • 图含有极大顶点数;
  • 具有极大顶点数的图包含依附于这些顶点的所有边,

树:无环连通图

  • 生成树(一个极小的连通子图):它含有图中全部的n个顶点,但只有足以构成一棵树的n - 1条边。(如果一个图有n个顶点和小于n —1条边,则是非连通图,如果它多于n—1条边,必定构成一个环)

  • 生成树森林:所有连通子图的生成树的集合

  • 有向树:如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1 ,则是一棵有向树。入度为0的顶点相当于树中的根结点,入度为1的顶点相当于树的双亲结点。

  • 有向生成森林:有向图的生成森林由若干棵有向树组成,含有图中全部顶点,但只有足以构成若千棵不相交的有向树的弧。

二:抽象数据类型

  • CreateGraph ( *G,V,VR):按照顶点集V和边弧集VR的定义构造图g。
  • DestroyGraph ( *G):图 G 存在则销毁。
  • LocateVex(G,u):若图G中存在顶点u,则返回图中的位置。
  • GetVex(G,v):返回图G中顶点v的值。
  • PutVex (G, v,value ):将图 G 中顶点 v 赋值 value。
  • FirstAdjVex (G,*v):返回顶点v的一个邻接顶点,若顶点在G中无邻接顶点返回空。
  • NextAdjVex(G,v,*w):返回顶点v相对于顶点w的下一个邻接顶点,若w是v的最后一个邻接点则返回“空”。
  • InsertVex ( *G, v ):在图G中增添新项点v。
  • DeleteVex (*G,v):刪除图G中顶点v及其相关的弧。
  • InsertArc (*G,v,w):在图G中增添弧<v,w>,若G是无向图,还需要增添对称弧<w,v>
  • DeleteArc (*G,v,w):在图G中删除弧<v,w>,若G是无向图,则还刪除对称弧<w,v>
  • DFSTraverse(G):对图G中进行深度优先遍历,在遍历过程对每个顶点调用。
  • HFSTraverse(G):对图G中进行广度优先遍历,在遍历过程对每个顶点调用。

三:存储结构

1. 邻接矩阵(适合稠密图,经常读取)

(1)定义
  1. 用一个一维数组存储图中n个顶点的信息
  2. 一个二维数组(称为邻接矩阵)存储图中的n×n条边或弧的信息
  • 无权值:如果两点之间有关联则为1,否则为0。
  • 有权值:如果两点之间有关联则为w,否则为∞,点与自身之间为0.
(2)特点
  • 矩阵的主对角线全为0
  • 无向图的矩阵是一个对称矩阵
  • 无向图中某个顶点vi的度就是矩阵的第i行或列的元素个数和。
  • 有向图中某个顶点vi的入度就是矩阵第i列的元素个数和,出度就是矩阵第i行的元素个数和
  • 某个顶点vi的邻接顶点就是矩阵中第i行中值为1/w的顶点
(3)属性
  • 顶点数组
  • 边或弧的二维数组
  • 顶点个数
  • 边或弧个数
(4)构建
  1. 根据顶点个数和顶点信息,初始化一维数组
  2. 根据边弧个数,初始化二维数组。根据权值和坐标赋值。
(5)实现
/**
 * 邻接矩阵
 */
public class MatrixGraph<T> {

    private T[] vertexs;    //顶点集

    private int vertexSize; //顶点个数

    private int[][] edges;    //边集

    private int edgeSize;   //边的个数

    private boolean isDirection = false;    //默认无向图

    private boolean isPower = false; //默认无权图

    public MatrixGraph(int vlen, int elen, boolean isDirection,boolean isPower) {
        vertexSize = 0;
        edgeSize = 0;
        this.isDirection = isDirection;
        this.isPower = isPower;
        vertexs = (T[])new Object[vlen];
        edges = new int[vlen][vlen];
        for (int i = 0; i < vertexs.length; i++) {
            vertexs[i] = null;
        }
        for (int i = 0; i < edges.length; i++) {
            for (int j = 0; j < edges[i].length; j++) {
                if (isPower) {
                    if (i == j)
                        edges[i][j] = 0;
                    else
                        edges[i][j] = Integer.MAX_VALUE;
                } else
                    edges[i][j] = 0;
            }
        }
    }

    public void resize(int max){
        T[] array1 =  (T[])new Object[max];
        for (int i = 0; i < array1.length; i++) {
            if (i < vertexs.length)
                array1[i] = vertexs[i];
            else
                array1[i] = null;
        }
        vertexs = array1;
        int[][] array2 = new int[max][max];
        for (int i = 0; i < array2.length; i++) {
            for (int j = 0; j < array2[i].length; j++) {
                if (i < edges.length && j < edges[i].length)
                    array2[i][j] = edges[i][j];
                else
                {
                    if (isPower) {
                        if (i == j)
                            array2[i][j] = 0;
                        else
                            array2[i][j] = Integer.MAX_VALUE;
                    } else
                        array2[i][j] = 0;
                }
            }
        }
        edges = array2;
    }

    //顶点集为空
    public boolean isEmptyVertexs() {
        return vertexSize == 0;
    }

    //边集为空
    public boolean isEmptyEdges() {
        return edgeSize == 0;
    }

    //是否包含顶点
    public boolean containsVertex(int index) {
        if (isEmptyVertexs())
            return false;
        return getVertex(index) != null;
    }

    //是否包含边
    public boolean containsEdge(int from, int to) {
        if (isEmptyEdges())
            return false;
        if (isDirection)
            return getEdge(from, to) != Integer.MAX_VALUE;
        else
            return getEdge(from, to) != 0;
    }

    //获取顶点
    public T getVertex(int index) {
        return vertexs[index];
    }

    //获取边
    public int getEdge(int from, int to) {
        return edges[from][to];
    }

    //获取顶点下标
    public int locateVertex(T key){
        int index = -1;
        for (int i = 0; i < vertexs.length; i++) {
            if (vertexs[i] != null && vertexs[i].equals(key))
                return i;
        }
        return index;
    }

    //添加顶点
    public void putVertex(int index, T vertex) {
        if (index < 0 || index > vertexs.length)
            return;
        if (vertexSize == vertexs.length)
            resize(vertexs.length * 2);
        if (!containsVertex(index))
            vertexSize++;
        vertexs[index] = vertex;
    }

    public void insertVertex(T vertex){0
        if (vertexSize == vertexs.length)
            resize(vertexs.length * 2);
        vertexs[vertexSize++] = vertex;
    }

    //添加边
    public void putEdge(T from, T to) {
        putEdge(from, to, 1);
    }

    public void putEdge(T from, T to, int power) {
        int fromIndex = locateVertex(from);
        int toIndex = locateVertex(to);
        if (fromIndex == -1 || toIndex == -1)
            return;
        if (!containsEdge(fromIndex, toIndex))
            edgeSize++;
        edges[fromIndex][toIndex] = power;
        if (!isDirection)
            edges[toIndex][fromIndex] = power;
    }

    //删除顶点
    public void delVertexs(T key){
        int index = locateVertex(key);
        if (index == -1)
            return;
        delVertexsByIndex(index);
    }

    public void delVertexsByIndex(int index){
        if (index < 0 || index > vertexs.length)
            return;
        if (!containsVertex(index))
            return;
        //同时要删除有关的边
        if (outDegree(vertexs[index]) > 0) {
            for (int j = 0; j < edges[index].length; j++) {
                if (edges[index][j] != 0 && edges[index][j] < Integer.MAX_VALUE) {
                    if (isPower)
                        edges[index][j] = Integer.MAX_VALUE;
                    else
                        edges[index][j] = 0;
                    edgeSize--;
                }
            }
        }
        if (inDegree(vertexs[index]) > 0) {
            for (int i = 0; i < edges.length; i++) {
                if (edges[i][index] != 0 && edges[index][index] < Integer.MAX_VALUE)
                {
                    if (isPower)
                        edges[i][index] = Integer.MAX_VALUE;
                    else
                        edges[i][index] = 0;
                    edgeSize--;
                }
            }
        }
        if (vertexSize == vertexs.length / 4)
            resize(vertexs.length / 2);
        vertexs[index] = null;
        vertexSize--;
    }

    //删除边
    public void delEdges(T from,T to){
        int fromIndex = locateVertex(from);
        int toIndex = locateVertex(to);
        if (fromIndex == -1 || toIndex == -1)
            return;
        if (!containsEdge(fromIndex,toIndex))
            return;
        if (isDirection)
            if (isPower)
                edges[fromIndex][toIndex] = Integer.MAX_VALUE;
            else
                edges[fromIndex][toIndex] = 0;
        else
        {
            if (isPower)
            {
                edges[fromIndex][toIndex] = Integer.MAX_VALUE;
                edges[toIndex][fromIndex] = Integer.MAX_VALUE;
            }
            else
            {
                edges[fromIndex][toIndex] = 0;
                edges[toIndex][fromIndex] = 0;
            }
        }
        edgeSize--;
    }

    //出度
    public int outDegree(T vertex){
        int index = locateVertex(vertex);
        if (index == -1)
            return -1;
        int degree = 0;
        for (int i = 0;i < edges[index].length;i++)
            if (edges[index][i] != 0 && edges[index][i] < Integer.MAX_VALUE)
                degree++;
        return degree;
    }

    //入度
    public int inDegree(T vertex){
        int index = locateVertex(vertex);
        if (index == -1)
            return -1;
        int degree = 0;
        for (int i = 0;i < edges.length;i++)
            if (edges[i][index] != 0 && edges[i][index] < Integer.MAX_VALUE)
                degree++;
        return degree;
    }

    //总度
    public int degree(T vertex){
        int index = locateVertex(vertex);
        if (index == -1)
            return -1;
        int degree = inDegree(vertex)+outDegree(vertex);
        if (isDirection)
            return degree;
        return degree / 2;
    }

    //最大度
    public int maxDegree(){
        int max = degree(vertexs[0]);
        for (int i = 1; i < vertexs.length; i++) {
            if (max < degree(vertexs[i]))
                max = degree(vertexs[i]);
        }
        return max;
    }

    //平均度
    public int avgDegree(){
        int sum = degree(vertexs[0]);
        for (int i = 1; i < vertexs.length; i++) {
            sum += degree(vertexs[i]);
        }
        return sum / vertexs.length;
    }

}

2. 邻接表(适合有向图不需要知道入度,稀疏图,经常修改)

(1)定义
  1. 用一个一维数组存储每个顶点,每个数据元素还需要存储指向第一个邻接点的指针,以便于査找该顶点的边信息。
  2. 用一个线性表存储每个顶点vi的所有邻接点,由于邻接点的个数不定,所以用单链表存储(无向图称为顶点Vi的边表,有向图则称为顶点Vi作为弧尾的出边表)。
(2)属性

顶点表:

  • 数据域:存储结点数据
  • 指针域:指向边表的第一个结点

边表:

  • 邻接点域:存储某顶点的邻接点在顶点表中的下标
  • 指针域:指向边表中下一个结点
  • 数据域:对于网图,存储结点的权值
(3)特点
  • 无向图中顶点的度就是顶点对应的边表的长度
  • 有向图中顶点的出度就是顶点对应的边表的长度
  • 顶点的邻接顶点就是顶点对应的边表的所有结点
(4)构建
  1. 根据顶点个数和顶点信息,初始化顶点表,并将顶点的边表置为空。
  2. 根据边弧个数初始化边表,根据坐标分别初始化结点i和j的指针域指向顶点表j和i,同时顶点表的j和i的指针域指向该结点i和j
(5)实现
/**
 * 邻接表
 * @param <T>
 */
public class LinkedGraph<T> {

    //结点数组,每个结点都有一条链表
    private LinkedLinear<T>[] linear;

    private int vertexSize;

    private int edgeSize;

    private boolean isDirection = false;

    private boolean isPower = false;

    public LinkedGraph(int len,boolean isDirection,boolean isPower){
        linear = new LinkedLinear[len];
        this.isDirection = isDirection;
        this.isPower = isPower;
        for (int i = 0; i < linear.length; i++) {
            linear[i] = new LinkedLinear<>();
        }
    }

    private void resize(int max){
        LinkedLinear<T>[] array = new LinkedLinear[max];
        for (int i = 0; i < array.length; i++) {
            if(i < linear.length && linear[i] !=null)
                array[i] = linear[i];
            else
                array[i] = new LinkedLinear<>();
        }
        linear = array;
    }

    //顶点是否空
    public boolean isVertexEmpty(){
        return vertexSize == 0;
    }

    //边是否空
    public boolean isEdgeEmpty(){
        return edgeSize == 0;
    }

    //是否包含顶点
    public boolean containsVertex(T vertex){
        return locateVertex(vertex) != -1;
    }

    //是否包含边
    public boolean containsEdge(T from,T to){
        if(!containsVertex(from) || !containsVertex(to))
            return false;
        int fromIndex = locateVertex(from);
        for (T vertex:
             linear[fromIndex]) {
            if(vertex.equals(to))
                return true;
        }
        return false;
    }

    //获取顶点
    public T getVertex(int index){
        if (index < 0 || index > linear.length)
            return null;
        return linear[index].getByIndex(0).getData();
    }

    //获取顶点索引
    public int locateVertex(T vertex){
        for (int i = 0; i < linear.length; i++) {
            T key = linear[i].getByIndex(0).getData();
            if (key.equals(vertex))
                return i;
        }
        return -1;
    }

    //加入顶点
    public void insertVertex(T vertex){
        if (vertexSize == linear.length)
            resize(linear.length * 2);
        linear[vertexSize++].add(vertex);

    }

    //加入边
    public void putEdge(T from,T to){
        putEdge(from, to,0);
    }

    public void putEdge(T from,T to,int power){
        if(!containsVertex(from) || !containsVertex(to) || containsEdge(from, to))
            return;
        int fromIndex = locateVertex(from);
        linear[fromIndex].add(to,power);
        if (!isDirection)
            putEdge(to,from,power);
        edgeSize++;
    }

    //删除顶点
    public void deleteVertex(T key){
        if(!containsVertex(key))
            return;
        int index = locateVertex(key);
        for (int i = index; i < linear.length - 1; i++) {
            linear[i] = linear[i+1];
        }
        vertexSize--;
        if (vertexSize == linear.length / 4)
            resize(linear.length / 2);
        //记得删除两条边
        for (int i = 0; i < linear.length; i++)
            linear[i].deleteByElement(key);
    }

    //删除边
    public void deleteEdge(T from,T to){
        if(!containsEdge(from, to))
            return;
        int fromIndex = locateVertex(from);
        linear[fromIndex].deleteByElement(to);
        edgeSize--;
        if (!isDirection)
            deleteEdge(to,from);
    }

    //出度
    public int outDegree(T vertex){
        int index = locateVertex(vertex);
        if (index < 0)
            return -1;
        return linear[index].getSize() - 1;
    }

    //入度
    public int inDegree(T vertex){
        int degree = 0;
        int index = locateVertex(vertex);
        if (index < 0)
            return -1;
        for (int i = 0; i < linear.length; i++) {
            if (i == index)
                continue;
            if(linear[i].get(vertex) != null)
                degree++;
        }
        return degree;
    }

    //总度
    public int degree(T vertex){
        int index = locateVertex(vertex);
        if (index == -1)
            return -1;
        int degree = inDegree(vertex)+outDegree(vertex);
        if (isDirection)
            return degree;
        return degree / 2;
    }

    //最大度
    public int maxDegree(){
        int max = degree(linear[0].getByIndex(0).getData());
        for (int i = 1; i < linear.length; i++) {
            int temp = degree(linear[i].getByIndex(0).getData());
            if (max < temp)
                max = temp;
        }
        return max;
    }

    //平均度
    public int avgDegree(){
        int sum = degree(linear[0].getByIndex(0).getData());
        for (int i = 1; i < linear.length; i++) {
            sum += degree(linear[i].getByIndex(0).getData());
        }
        return sum / vertexSize;
    }
}

3. 十字链表

定义和属性
  1. 用一个一维数组存储每个顶点,每个数据元素需要存储一个入边表的头指针,和一个出边表的头指针。
  2. 用一个单链表存储每个顶点vi的所有邻接点,用两个数据域表示弧的起点和终点在顶点表的下标,用两个指针域表示终点相同的下一条边和起点相同的下一条边。还可以用一个数据域表示结点的权值。
特点

十字链表把邻接表和逆邻接表整合在了一起,这样既容易找到以vi为尾的弧,也容易找到以vi为头的弧,因而容易求得顶点的出度和入度。而且它的时间复杂度和邻接表相同。

4. 邻接多重表(适合无向图对边操作)

定义

在十字链表中的边表中,对于(顶点1,顶点2)用两个数据域表示与其依附的顶点1和顶点2在顶点表中下标,用指针域分别指向依附的顶点1的下一条边(顶点1,顶点3)和依附顶点2的下一条边(顶点2,顶点4)在边表的结点。

特点

在边表中只用了一个顶点表示边,若想删除边,只需将原来指向它的指针指向空即可。

5. 边集数组(适合对边操作)

由两个一维数组构成。一个是存储顶点的倌息;另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标、终点下标和权组成。

四:遍历

从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次。

1. 深度遍历(DFS)——树的前序遍历

定义

连通图:它从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。

非连通图:只需对它的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次深度优先遍历后,若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

过程
  1. 对点集合进行遍历,初始化所有点未被访问。从点V1开始,标记V1。
  2. 邻接矩阵:对边集合进行遍历,如果存在点V2与点V1关联的边且没有标记,则递归V2。
  3. 邻接表:从顶点表中V1的边表的头结点V2开始,对边表进行遍历。如果没有标记,则递归V2。
特点
  • 所需要的时间与每个顶点的度数成正比
  • 需要访问两次边
  • 对于点多边少的稀疏图来说,邻接表结构使得算法在时间效率上大大提髙。
  • 适合目标明确的情况

2.广度优先遍历(BFS)——树的层序遍历 / 队列

定义

连通图:它从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发广度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。

非连通图:只需对它的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次广度优先遍历后,若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止

过程
  1. 初始化一个辅助的队列Q
  2. 对点集合进行遍历,初始化所有点未被访问。从点V1开始,标记V1并加入队列。
  3. 邻接矩阵:如果队列不为空,则将队头元素出队,对边集合进行遍历,如果存在点V2与点V1关联的边且没有标记,则标记V2并加入队列,继续循环。
  4. 邻接表:如果队列不为空,则将队头元素V1出队,从顶点表中队头元素V1的边表的头结点V2开始,对边表进行遍历。如果没有标记,则标记V2并加入队列。
特点
  • 所需要的时间与每个顶点的度数成正比
  • 更适合在不断扩大遍历范围时找到相对最优解的情况。

五:最小生成树(代价最小)

1. 定义:构造连通网的最小代价生成树

2. 构造

(1)普里姆算法(稠密图)
定义:以某顶点为起点,逐步找每个顶点上最小权值的边来构建

假设N= (P,{E})是连通网,TE是N上最小生成树中边的集合。算法从U={u0}(u0 € V), TE={}开始。重复执行下述操作:在所有u€U, v€V-U的边(u,v) 中找一条代价最小的边(u0 , v0)并入集合TE,同时v0并入U ,直至U =V为止。此时TE中必有n—1条边,则T= (V,{TE})为N的最小生成树。

过程
  1. 创建:创建两个数组A B分别保存顶点下标和边的权值。将v0加入生成树,A和B的第一个元素为0
  2. 初始化:对顶点集合进行遍历,将A初始化为v0的下标,将与v0有关的边的权值加入数组B
  3. 找最小值:对顶点V1的边集合进行遍历,初始化最小权值min,如果权值不为0,也就是说还未加入的顶点且权值小于min,则赋值最小权值并保存下标i。遍历后获取到与V1有关的边中权值最小的边和顶点Vi,并把结点 Vi 加入到生成树中。
  4. 更新权值:对顶点Vi的边集合进行遍历,如果权值不为0,也就是说还未加入的顶点且权值小于B中已有的权值,则将较小值保存在数组B中,把下标i保存在数组A中。
  5. 继续对其他结点遍历
  6. 最小生成树:由数组A获取,生成树包含边(A[i],i)
(2)克鲁斯卡尔算法(稀疏图)
定义:逐步找最小边

假设N=(V,{E})是连通网,则令最小生成树的初始状态为只有n个顶点而无边的非连通图T={V,{}},图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边而选择下一条代价最小的边。依次类推,直至T中所有顶点都在同一连通分量上为止。

过程
  1. 初始化:将图转化为边集数组E,并且对它们按权值从小到大排序。创建数组A判断是否成环,初始化为0。
  2. 判断成环:对图的已排序边集数组E进行遍历。判断边(a,b)与生成树是否会成环:通过数组A找到已加入树的结点a和结点b的连线尾端结点,如果相同,则证明如果加入边(a,b)将会有顶点重复,就会成环,则跳过这条边。
  3. 找最小边:若不会成环,则将边的尾结点放入下标为起点的A中,表示边已经加入二叉树。
  4. 最小生成树:由A获取,下标 i 的值 A[i] 表示生成树包含边(i,A[I])

六:最短路径

1. 定义

非网图: 指两顶点之间经过的边数最少的路径。
网图: 指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。

2. 方法

(1)迪杰斯特拉(Dijkstra )算法(一点与所有顶点之间,非负权图)
定义

基于已经求出的最短路径的基础上,求得更远顶点的最短路径。

过程
  1. 创建:创建两个数组A B分别保存顶点下标和边的权值。
  2. 初始化:从顶点v0开始,遍历顶点集合,初始化数组C标记所有顶点未找到最短路径,如果有边依附于v0则将边的权值赋值给数组B。
  3. 获取靠近的顶点:遍历顶点集合,寻找离v0最近的顶点。如果顶点i的权值小于min且顶点i未标记,则更新最小值。遍历后获取最近的顶点vi,对其标记。
  4. 修正最小值:遍历顶点集合,如果v0经过vi到达未被标记顶点的路径比B中的现在这条直接到达顶点的路径还短,则修正B和A
  5. 最短路径:v0到vi的最短路径可以看D[i],A[i]表示v0经过A[i]到达vi
(2)弗洛伊德算法(所有顶点与所有顶点之间)
定义

用一个二重循环初始化,用一个三重循环权值修正,就完成了所有顶点到所有顶点的最短路径计算。

流程
  1. 初始化:创建并初始化一个用来存储权值的矩阵A和一个用来存储路径的矩阵B
  2. 修正最小值:对于每个顶点再遍历顶点集合,如果经过下标为i的顶点比原来的路径更短,则修改A和B。
  3. 最短路径:vi到vj的最短路径可以看 B[i][j],不断对行 i 循环,直到B[i][j]=j

七:拓扑排序(顺序进行工程)

1. 基本概念

  • AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网。(AOV网没有回路)

  • 拓扑序列:设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列v1,v2……vn,满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前。则我们称这样的顶点序列为一个拓扑序列。

  • 拓扑排序:对一个有向图构造拓扑序列的过程。

2. 排序

定义:

从AOV网中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧,继续重复此步骤,直到输出全部顶点或者AOV网中不存在入度为0的顶点为止。

过程:
  1. 创建并初始化一个栈,用于存储入度为0的顶点
  2. 对栈进行遍历,将元素v弹出栈并输出。
  3. 对v的边表进行遍历,并把边表的结点vi的入度-1(也就是删除结点v为尾的弧),若入度为0则入栈
  4. 如果输出的结点等于图的总结点数,则存在环。

八:关键路径

1. 概念

  • AOE网:在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网。把AOE网中没有入边的顶点称为始点或源点,没有出边的顶点称为终点或汇点。

  • 关键路径:AOE网的路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。(可能有多条)

  • 事件的最早发生时间etv:即顶点vi的最早发生时间。从源点v0到vj的最长路径长度(时间),决定了活动的最早开始时间

  • 事件的最晚发生时间ltv:即顶点vi的最晚发生时间。从源点v0到汇点vn的最晚发生时间逐步逆推到v(j+1)的最晚发生时间

  • 活动的最早开工时间ete:即弧ai的最早发生时间。也就是顶点vi的最早发生时间(从起点0正推,如果有多个时间取最大值)

  • 活动的最晚开工时间lte:即弧ai的最晚发生时间。也就是下一顶点vj的最晚发生时间-vi到vj所需的时间(从终点最早开始时间逆推,如果有多个时间取最小值)

  • 如果ete=lte,则证明该活动是关键活动,不能延迟。

  • 松弛时间:最晚开始时间-最早开始时间

2. 过程

首先:求事件的最早发生时间etv的过程,就是我们从头至尾找拓扑序列的过程

  1. 创建两个数组A和B,用于保存事件最早发生时间和最迟发生时间,初始化A为0。创建一个栈C存储入度为0的顶点,创建一个栈D,用于保存序列。
  2. 对栈C进行遍历,将元素v弹出栈并压入栈D。
  3. 对v的边表进行遍历,并把边表的结点vi的入度-1(也就是删除结点v为尾的弧),若入度为0则入栈C。如果经过结点vi的路径的发生时间比原先的最早发生时间更大,则更新最早发生时间A。(可以晚点的开始活动)

再求最短路径:

  1. 声明活动最早发生时间和最晚发生时间,初始化B为最早发生时间的最大值
  2. 对栈D进行遍历,将元素v弹出栈
  3. 对v的边表进行遍历,如果不要经过结点vi的路径的发生时间比原先的最晚发生时间更小,则更新最晚发生时间B。(必须要更早的完成活动)
  4. 对于每个顶点vi的边表进行遍历vj,最早发生时间为vi的最早时间,最晚发生时间为vj的最晚时间再减去经过此顶点权值,如果vi和vj相同,则在关键路径上
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值