249 阅读15分钟

为了巩固知识,做的笔记,还没有完善,如有错误请指正

一.图的概念

1.图G的定义:
(1)图由两个集合V,E组成,记为G=(V,E),V是有穷非空的顶点集,E是偶对顶点之间的有穷边集。
(2)通常情况下V(G)表示图G的顶点集合,E(G)表示图G的边集合,E(G)可以为空。(只有一个点也是图)
2.有向图和无向图

image.png 1.有向图<>
有向图中顶点对是有序的,<x,y>与<y,x>是不同的两条边。对于<x,y>称为从顶点x到顶点y的有向边,x是有向边的始点,y是有向边的终点。<x,y>也可以称为弧,x为弧尾,y为弧头。
2.无向图()
无向图中顶点对(x,y)是无序对,它称之为顶点x与顶点y相关联的一条边。(x,y)和(y,x)是同一条边。

二.图的基本术语

1.子图

G1的边集和点集,都是G2图的子集时,我们称G1为G2的子图。如图T1,T2,T3,T4都是G的子图。

image.png

2.完全图

(1)无向完全图

有n个节点,且边数为n(n-1)/2条边的无向图。

image.png

(2)有向完全图

有n个节点,且边数为n(n-1)条边的有向图。

image.png

3.稀疏图和稠密图

顶点之间边数很少的图称为稀疏图,反之称为稠密图。

4.邻接点

对于无向图(有向图也存在),若(v1,v2)属于其边集E,那么称v1,v2互为邻接点。即无向图有直接连线连线的两个点。

5.顶点v的度,入度,出度

(1)度(无向图):与点v相关联的边的条数。记为TD(v)。
(2)入度(有向图):指向v顶点的弧数。记为ID(v)。
(3)出度(有向图):从该顶点指向其邻接点的弧数。OD(v)。
对于有向图:TD(v)=ID(v)+OD(v)
对于无向图其边数等于度数的一半,即:e=(TD(V1)+TD(V2)+...+TD(Vn))/2

6.路径和路径长度

7.权和网

(1)权:每条边上具有某种实际意义的数值,例如时间,路程,花费。
(2)网:带权图称为网。

8.回路或环

9.简单路径、简单回路或环

10.连通、连通图和联通分量(连通图一定是无向图)

(1)连通: 在无向图G中,若点v1和v2之间有路径则称v1,v2是连通的。
(2)连通图: 图中任意两个顶点(vi到vj,vj到vi)都是连通的(都有路径),则该图称为连通图。
(3)连通分量: 若无向图为非连通图,则无向图中的极大连通子图称为连通分量,极大连通子图不唯一。 极大连通子图:连通的子图,但不存在一条e(e属于E),使其仍然连通。

image.png

11.强连通图和强连通分量(强连通和弱连通的概念只在有向图中存在)

(1)强连通图:在有向图G中,每一对vi,vj(vi,vj属于V,vi!=vj),从vi到vj和vj到vi都有路径,则称G是强连通图。

image.png (2)强连通分量:有向图中的极大强连通子图称为强连通分量。与强连通图不同,极大强连通子图允许vi=vj,单一顶点v也是极大强连通分量。
注:不是强连通图也可以有强连通分量,极大强连通子图也是强连通

1898892-20200121145503235-1288417486.jpg

12.连通图的生成树

含有图中全部顶点,但只有足以构成一棵树的n-1条边的图。
注:强连通图的生成树也是极小连通图(再少一条边就必定不连通的子图)

13.有向树和生成森林

(1)有向树:有一个顶点入度为0,其余顶点的入度均为1的有向图称为有向树。
(2)生成森林:由若干个有向树组成,含有有向图中所有节点,但只有足以构成各自树且间不相交

二.案例引入

六度空间

三.图的存储结构

1.邻接矩阵

(1)邻接矩阵表示法:

a.图的邻接矩阵:表示顶点之间关系的矩阵,若两点之间存在边或弧,则两点下标对应的矩阵值为1,否则为0

image.png b.网(带权图)的邻接矩阵:表示顶点之间关系的矩阵,若两点之间存在边或弧,则两点下标对应的矩阵值为权值,否则为无穷(远远大于任意权值的数)。

image.png

(2)图的邻接矩阵存储结构

  • 一个二维数组存放矩阵的值。
  • 一个一维数组存储顶点的信息(v1,v2...)。
//图的邻接矩阵存储表示
#define MaxInt 74776         //表示无穷
#define MVNum 100            //最大顶点数
typedef char VerTexType;     //将顶点的数据类型设为char且名字为VerTexType
typedef int ArcType;         //将权值的数据类型设为int且名字为ArcType
typedef struct{
    VerTexType vexs[MVNum];  //顶点表
    ArcType arcs[MVNum][MAVnum]; //邻接矩阵
    int vexnum,arcnum;  //存储图的顶点数和边数
    }AMGraph;

(3)邻接矩阵创建无向网

  • 输入顶点数和边数
  • 将各顶点的信息(v1,v2...)录入顶点表
  • 初始化邻接矩阵。将矩阵值全部置为MaxInt
  • 输入想要录入权值的边及其依附点(依附点的值就是邻接矩阵的数组下标)
  • 根据数组下标对应的边,将权值录入
int CreateUDN(AMGraph &G){
    cin>>G.vexnum>>G.arcnum;   //输入顶点数和边数
    for(int i=0;i<G.vexnum;i++)
        cin>>vexs[i];          //录入顶点值信息
    for(int i=0;i<G.vexnum;i++){
        for(int j=0;i<G.vexnum;j++){
            arcs[i][j]=MaxInt; //初始化邻接矩阵,边的权值全部设为MaxInt;
            }
        }
    for(int k=0;i<G.arcnum;k++){
        cin>>v1>>v2>>w;        //输入边的依附点和边的权值
        i=LocateVex(G,v1);j=LocateVex(G,v2);//确定V1,V2的位置,即数组的下标
        arcs[i][j]=w;                       //边<v1,v2>的权值置为w
        arcs[j][i]=arcs[i][j];//<v1,v2>,<v2,v1>的权值都为W
        }
        return 1;        
}

(4)邻接矩阵表示法的优缺点

优点

  • 可以根据A[i][j]=0或1判断两个顶点间是否有边。
  • 便于判断各个点的度数,对于无向图,矩阵第i行元素之和就是顶点Vi的度数。对于有向图,矩阵第i行元素之和为出度,第i列元素之和就是入度。
    缺点
  • 不便于增加删除节点
  • 不便于统计边的数目,需要遍历矩阵所有元素。对于无向图遍历完所有元素得到图的总度数除以2得到边数,对于有向图遍历完得到的总度数就是边数。
  • 空间复杂度为n的平方,浪费空间。

2.邻接表

(1)邻接表的表示法

邻接表由表头节点表边链表组成,表头节点依次存储图中的所有顶点,边链表记录某一个顶点的所有关联点。(表的每一行记录每一个点的发散)

  • 表头节点表:包括数据域和链域,数据域用来存放顶点的信息,链域用来指向第一个节点。为了可以随机访问任一顶点的边链表,表头节点以顺序形式存储 。另外为了方便获取每个边链表的节点个数,可以在头节点处再开辟一块数据域来存放边界点的数量。
  • 边节点链表:由数据域(info)、链域(nextArc)、邻接点域(adjVex)组成,邻接点域用来存放与顶点邻接的点的位置(表头节点的索引),链域存放下一个邻接点的地址,数据域用来存放边的相关信息(权值)。边链表节点之间没什么关联,都只与表头节点关联。

image.png image.png

(2)邻接表的存储结构

#define MVNum 100
typedef char VerTexType;
typedef int ArcType;
typedef int Otherinfo;
typedef struct ArcNode{ //存储边节点
    int adjVex;               //存储节点的序号
    struct ArcNode *nextarc;  //存储下一个节点的地址
    Otherinfo info;           //存储边的相关信息
}ArcNode;
typedef struct VNode{ /存储表头节点信息
    VerTexType data;          //存储表头节点的信息
    ArcNode *firstarc;        //表头节点的指针域
}VNode,AdjList[MVNum];        //AdjList可以使表头节点以数组的形式存储
typedef struct{ //存储图的信息
    AdjList vertices;         //定义了AdjList类型的数组存储结构,存储边节点的数据域和指针域信息
    int vexnum,arcnum;        //记录顶点数和边数
}ALGraph;

(3)邻接表表示法创建无向图

int CreateUDG(ALGgraph &G){
    cin>>G.vexnum>>G.arcnum;        //输入图的节点数和边数
    for(int i=0;i<G.vexnum;++i){
       cin>> G.vertices[i].data;    //将顶点的信息录入到表头节点表中
       G.vertices[i].firstarc=NULL; //初始化表头节点的指针域
       }
    for(int k=0;k<G.arcnum;++k){
        cin>>v1>>v2;                //输入边的两个依附点
        i=LocateVex(G,v1); j=LocateVex(G,v2);
        //找到v1,v2的序号
        ArcNode p1=new ArcNode;
        p1->adjvex=j;
        p1->nextarc=G.vertices[i].firstarc; G.vertices[i].firstarc=p1;
        //将边节点v2插入到表头节点v1的后面(v1,v2)
        ArcNode p2=new ArcNode;
        p2->adjvex=i;
        p2->nextarc=G.vertices[j].firstarc; G.vertices[j].firstarc=p2;
        //将边节点v1插入到表头节点v2的后面(v2,v1)
        }
        return 1;
        }  

(4)邻接表表示法的优缺点

优点:

  • 便于增加删除节点。
  • 便于统计边的条数:按顶点表顺序顺序查找所有边即可得到边的目数。(有向图直接遍历,无向图遍历完成后总数除以2)---时间复杂度为O(n+e)。
  • 存储空间效率高,适合表示稀疏表。

缺点:

  • 不便于直接查看两点vi和vj之间是否有边,若要查看必须遍历第i个边表,最坏情况下时间复杂度为O(n)。
  • 不便于统计顶点的度数(求出度容易,入度困难):对于无向图,顶点vi的度是第i个边表中节点个数;对于有向图,顶点vi的出度为是第i个边表的节点个数,入度需要遍历各个顶点的边表,查看各个顶点的边表节点是否含有vi。

四.图的遍历

前言:

  • 图的遍历算法是求解图的连通性问题、拓扑排序和关键路径等算法的基础。
  • 由于图的任意节点都可能和其余的顶点连接,图的顶点可能会被访问多次,需要设置一个visited[]数组标记该顶点是否已经被访问--ture(1)已经被访问,false(0)未被访问。

1.深度优先搜索---树的先序遍历的推广

(1)遍历步骤

  • a:从图中选一个节点作为顶点出发。
  • b:访问该顶点第一个未被访问过的邻接点,访问该邻接点后,以该邻接点为新的顶点,继续访问该顶点第一个未被访问过的节点。
  • c:重复步骤b,直至有一个顶点的邻接点全被访问过。
  • d:返回该顶点前一个且仍有未被访问邻接点的顶点。
  • e: 重复b,c,d直至所有节点都被访问过。

(2)算法实现

从图中选择一个顶点v出发,访问v,并设置visited[v]=ture(1)

深度优先遍历的抽象表示:

连通图
非连通图

深度优先遍历的具体表示:

邻接矩阵表示
邻接表表示

(3)算法分析---时间复杂度与查找的次数有关

  • 邻接矩阵:邻接矩阵因为对每个顶点 v 都要通过遍历一次一维数组 matrix[v][ ]找到它的所有邻接点,对应的时间复杂度O ( n ),所以总的时间复杂度Ο(n^2)。
  • 邻接表:邻接表因为本身存储的就是有相连关系的邻接点,所以查找所有顶点的邻接点的时间复杂度为O ( e )。又因为对每个顶点都要进行一次查找,所以总的时间复杂度O ( n + e )。

2.广度优先搜索---树的先序遍历的推广

(1)遍历步骤

  • a.从图中的一个顶点 v出发并访问。
  • b.访问顶点 v的所有邻接点。
  • c.按照邻接点的访问顺序,访问以邻接点为顶点的所有未被访问的邻接点。
  • d.重复步骤c,直到图中所有的顶点都被访问。

(2)算法实现

(3)算法分析---时间复杂度与查找的次数有关

  • 邻接矩阵:邻接矩阵因为对每个顶点 v 都要通过遍历一次一维数组 matrix[v][ ]找到它的所有邻接点,对应的时间复杂度O ( n ),所以总的时间复杂度Ο(n^2)。
  • 邻接表:邻接表因为本身存储的就是有相连关系的邻接点,所以查找所有顶点的邻接点的时间复杂度为O ( e )。又因为对每个顶点都要进行一次查找,所以总的时间复杂度O ( n + e )。

五.图的应用

1.最小生成树---最小代价生成树(没有回路)

最小生成树定义:在一个连通网的所有生成树中,各边代价之和最小的生成树。

(1)普利姆算法

  • 算法思想:加点法
  • 算法步骤:
    a.从图中选一个节点出发。
    b.连接与该节点有关联的权值最小的节点。
    c.比较各个已连接的节点与未连接节点的权值,选择最小的连接(形成回路的边不连接)。
    d.重复c步骤,直至有n-1条边被连接。(n为顶点个数)
  • 算法实现

image.png

(2)克鲁斯卡尔算法

  • 算法思想:加边法
  • 算法步骤:
    从所有边中,选择未被选中的,权值最小边e,边e的关联点不能在同一个连通分量中(不能形成回路),直至有n-1条边被选中(n为顶点个数)。
  • 算法实现

image.png

image.png

image.png

2.最短路径

(1)迪杰斯特拉算法---单源最短路径--->从一个顶点到其余各个顶点的最短路径

  • 算法思想:中转节点法
  • 算法步骤:
    a.设置两个顶点的集合S和U,集合S中存放已找到最短路径的顶点(也是下一轮计算中可以经过的节点),集合U中存放当前还未找到最短路径的顶点。初始状态时,集合S中只包含源点,设为v0.
    b.计算该轮源点v0到U中所有节点的路径长度(不能到达的记为MAXINT),将最短的路径的末节点放入到集合S中。
    c.不断重复b步骤,直到集合U中的顶点全部加入到集合S中为止。
  • 算法实现

image.png

例题
image.png

image.png
先计算第一个顶点v0到其他顶点的路径长度,将最短的路径长度的末节点v1放入S中;然后以末节点v1为新的节点,找该节点到其他节点的最短路径,将该路径的末节v2点放入S中。v0到v2的最短路径=v0到v1最短路径+v1到v2最短路径之和

(2)弗洛伊德算法(解封算法)---多源头最短路径--->图中每一对顶点之间的最短路径

  • 算法思想:解封顶点法
  • 算法步骤:
  • 算法实现

3.拓扑排序

定义:拓扑排序就是将AOV-网中所有顶点排成一个线性序列,该序列满足:若在AOV-网中从顶点vi到顶点vj有一条 路径,则该线性序列中的顶点vi必定在顶点vj之前。

顶点活动网(AOV网) :将顶点表示活动,边表示活动之间的关系的网称为顶点活动网。

  • 算法思想:0入删除法
  • 算法步骤:
    (1)在有向图中选一个入度为0的顶点输出。
    (2)从图中删除该顶点及所有它的出边(多个顶点都可以删除时,一般先删除序号较小的)。
    (3)重复执行1和2,直到全部顶点均已输出,或图中剩余顶点的入度均不为0(说明图中存在回路,无法继续拓扑排序)。
  • 算法实现

4.关键路径

AOE网:带权的有向图,顶点表示事件,边表示活动,权表示活动持续的时间。

顶点vi表示事件
边<vi,vj>表示活动
边的活动完成后,相应的点的事件也完成,活动的发生是为了完成事件。

关键路径:在AOE网中,从始点到终点具有最大路径长度(该路径上的各个活动所持续的时间之和)的路径称为关键路径。
关键活动:关键路径上的活动称为关键活动。

事件的最早完成时间=源点到该点的最大路径长度(汇入该点的所有路径都完成事件才能发生)
事件的最迟完成时间=AOE网的关键路径-汇点到该点的最大路径长度(给最长路径留足够的时间完成)。
活动的最早发生时间=该活动的始点到源点的最大路径长度,活动的最早发生时间等于活动左端点的事件最早发生事件
活动的最迟发生时间=该活动终点事件的最迟发生时间-活动的持续时间(权值)

求关键活动:活动的最早发生时间与最晚发生时间相当的活动为关键活动。

image.png