基本概念
1. 图的定义:图G由顶点集V和边集E构成。点的个数也称为阶。注,图不能为空!点数不能为零,但是边数可以为零。
2. 有向图:有向边,<v, w> v为弧尾,w为弧头。
3. 无向图:图中的边无方向,<v, w>和<w, v>指的是一条边
4. 简单图:图中没有重复的边,也不存在顶点到自身的边
5. 多重图:和简单图的定义是相对的,也就是图中的两个结点之间的边数多于一条,也允许顶点通过一条边和自己关联
6. 完全图:任意两个顶点之间都存在边。无向完全图:n个顶点,有n(n-1)/2条边;有向完全图,任意两个顶点之间有方向相反的两条边。N个顶点,有n(n-1)条边。
7. 子图:两个图G=(V, E)和G’=(V’, E’),如果V’是V的子集,E‘是E的子集,那么G‘是G的子图,如果V = V’,那么G’为G的生成子图。注,V和E的子集并一定会构成一个子图,因为有可能组成的不是一个图。
8. 连通、连通图和连通分量:如果顶点v和顶点w之间有路径存在,说明v和w是连通的,如果图中任意两点之间都是连通的,那么称图G为连通图。无向图中的极大连通子图称为连通分量。对于无向图而言,n个顶点,如果边数小于n-1,那么一定是非连通图。注:极大连通子图为无向图的连通分量,极大要求该连通子图包括其所有的边。极小则既要求无向图的连通性,又要求图中的边数最少
9. 强连通图、强连通分量:有向图中,从顶点v到顶点w和从顶点w到顶点v之间都有路径,则称这两个结点之间是强连通的。如果有向图中,任意两个结点都是强连通的,那么称该图为强连通图。有向图中的极大连通子图为有向图的强连通分量。注:连通性一般指的是无向图、强连通性指的是有向图。
10. 生成树、生成森林:生成树是包含图中所有结点的一个极小连通子图。如果点数为n,则边数一定是n-1。对于生成树而言,砍去一条边就是非连通图,增加一条边,就会形成一个回路。在非联通图中,联通分量的生成树构成了非联通图的生成森林
11. 顶点的度、入度和出度:度为以该顶点为一个端点的边的个数。入度则是以顶点v为终点的有向边,出度则是以顶点v为起点的有向边。在无向图中,度数之和为边数之和的2倍;在有向图中,入度之和=出度之和=边数。
12. 边的权和网:边上赋予一定含义的数值,该数值称为权。带有权重的图也称为网。
13. 路径、路径长度和回路:顶点v到顶点w之间所有边的个数为路径长度,第一个顶点和最后一个顶点相同的路径称为回路或者环。在一个无向图中,如果边数大于n-1,则一定存在回路。
14. 简单路径、简单回路:路径中不存在重复顶点的为简单路径;除第一个和最后一个顶点之外,其余顶点不重复出现的回路为简单回路。
15. 距离:从顶点v出发到顶点w的最短路径若存在,则此路径称为v到w的距离,如果两点不连通,记该距离为无穷大
16. 有向树:有一个顶点入度为0,其余顶点的入度均为1的有向图称为有向树。
邻接矩阵法(顺序存储)
用一个一维矩阵表示图的结点,用一个二维矩阵表示图中边的信息。对于无权图,两个结点之间有边,则用1 表示;没有就用0表示。对于有权图,有边就用权值替代,没有边就用无穷大表示。
l 对于无向图,邻接矩阵为对称矩阵。
l 对于无向图,第i行的1之和表示该结点的度,因为是对称矩阵,第i列的1之和也是。
l 对于有向图,第i行的1之和表示该结点的出度,第i列的1之和为该结点的入度。
l 邻接矩阵很容易知道两个结点之间是否存在边。
邻接表法(链表法)
有顶点表和边表。对图中每个顶点都建立一个单链表,链表中存放该顶点的所有边。因此对于无向图而言,空间为n+2e;对于有向图,空间为n+e。
顶点结构:
顶点信息 | 边表头指针 |
边结构:
邻接点域 | 指针域 |
对于邻接表法,有向图的顶点的边表结点都是以该结点为尾结点的边。
图的邻接表法不是唯一的,但是由一个邻接表可以得到唯一的图。
邻接表很容易知道一个顶点的所有边,在邻接矩阵中则需要遍历某一行才可得,所用时间为O(n),但是在邻接表中寻找两个结点之间是否存在边,则比较费劲。另外对于有向图而言,寻找一个顶点的所有出边很简单,但是寻找一个结点的所有入边,就得遍历所有的边表。
十字链表(有向图的另一种链表法)
对应弧和顶点各有一种数据结构相对应。
弧结构:
尾结点 | 头结点 | 相同头结点的下一条弧 | 相同尾结点的下一条弧 |
顶点结构:
顶点相关信息 | 以该顶点为头的第一条弧 | 以该顶点为尾的第一条弧 |
十字链表中,弧头相同的弧在一条链表上;弧尾相同的弧在一条链表上
容易得到一个顶点的出度和入度。
十字链表并不是唯一的,但是一个十字链表可以得到唯一的图。
邻接多重表(无向图的另一种链表法)
因为在邻接表中不方便查找两个顶点之间是否存在边,用临接多重表就方便多了
边结构:
边相连的一个顶点A | 依附于A点的另一条边 | 边相连的另一个顶点B | 依附于B点的另一条边 |
点结构:
顶点的信息 | 依附于该顶点的第一条边 |
邻接多重表中,同一个顶点的边串联在一个链条上,以为每条边依附于两个顶点,所以每个边结点同时链接在两个链表中。
容易找到每个顶点的边数,以及两个顶点之间是否存在边。
广度优先搜索【BFS Breadth-First-Search】
BFS遍历相当于树的层次遍历,不是递归的算法,因此需要一个队列进行辅助。相似的思想还被用于Dijkstra单元最短路径和Prim最小生成树算法。
l 当采用邻接表作为存储方式时,每个顶点访问一次,每条边至少访问依次一次。因此时间复杂度为O(n+e)。当采用邻接矩阵作为存储方式时,每个点访问一次,查找每个点的邻接点也要O(n),所以时间复杂度为O()
l 广度优先生成树:因为给定图的邻接矩阵存储方式唯一,所以其广度优先生成树也是唯一的;因为图的邻接表存储方式不唯一,所以其广度优先生成树不是唯一的。
l 对于无向图的广度优先生成树,起点到所有顶点的路径都是最短路径,也是所有生成树中高度最小的。
l 使用BFS可以求非带权图的单源最短路径
深度优先搜索【Depth-First-Search、DFS】
l DFS类似于树的先序遍历。是递归的算法,但是要借助一个递归栈来进行,因为有依次退回。
l 当采用邻接矩阵村存储时,因为主要是找邻接点,找每个顶点邻接点需要O(n),所以时间复杂度为O()。对于邻接表而言,找每个顶点的邻接点所用时间为O(e),所以时间复杂度为O(n+e)。
l 对于同样一个图,因为邻接矩阵的表达是唯一的,所以DFS和BFS遍历的序列是唯一的;但是邻接表的表示不唯一,所以基于邻接表得到的DFS和BFS的序列是不唯一的。
l 深度优先生成树和生成森林
l 对于连通图而言,调用DFS会产生深度优先生成树,否则就是深度优先生成森林。
l 用图的遍历可以判断图是否是连通的。对于无向图而言,连通分量数就是主函数中调用DFS或BFS的次数,但是对于有向图是不同的,因为有向图分强连通和非强连通。对于非强连通分量,调用一次DFS或BFS是没办法访问到该非强连通分量的所有顶点的。
l 利用深度优先遍历可以判断图是否存在回路
最小生成树
l 生成树是图的极小连通子图。包含图中所有顶点,并且只含有尽可能少的边。所以对于生成树,如果砍掉一条边,则会变成非联通的;若增加一条边,则会在图中形成回路。
l 对于一个带权连通无向图,生成树不同,每棵树上的权重之和也可能不同。其中边的权值之和最小的生成树为最小生成树。
l 最小生成树不是唯一的,它的树形可能不同。对于权值均不相同的的图来说,其最小生成树是唯一的。
l 最小生成树的权值之和一定是相同的,这是它的定义所形成的性质。
l 最小生成树的算法主要有Prim算法和Kruskal算法,两者都是基于贪心算法的策略。
Prim算法(最小生成树,O())
l 着重于点的一个算法,不依赖于边。时间复杂度为O()。适合边稠密的图
l 顶点集V中先加入一个顶点,边集E初始化为空。然后从V中存在的顶点到不存在的顶点之间,寻找权值最小的一条边,加入E中。重复该过程,直到V中包含图G中的所有顶点。
Kruskal算法(最小生成树,O())
l 着重于边的一个算法,时间复杂度为:O() 在该算法中,用堆存放边的集合。
l 初始化顶点集V为空,边集E也是空。从图的边集和树的边集的差集中寻找一条权值最小的边,加入E中,然后将该边依附的两个顶点也加入V中。重复以上过程,直到V包含图中所有顶点。
最短路径
对于无权图,可以用广度优先搜索来实现。但是对于带权图则没办法用该方法实现
l 带权路径长度:从一个顶点A到其余任意顶点B的一条路径上所经过边上的权值之和定义为该路径的带权路径长度
l 最短路径:带权路径长度最短的那条路径,最短路径是允许有环的
l 单源最短路径:Dijkstra算法
l 每对顶点间的最短路径:Floyd算法
Dijkstra算法(单源最短路径,O())
l 时间复杂度为O(),如果想得到任意顶点之间的最短路径,时间复杂度为O()
l 算法基于贪心策略,对于权值为负数的图是不适用的,可以求解有回路的带权图
l 算法思想:用一个集合S来表示已求得的最短路径的顶点,用一个数组D表示所求点到该顶点的最短路径。D[j]表示顶点i到顶点j的最短路径。初始化的时候,两点之间如果有边,就赋值为其权重,没有边就赋值为无穷大。然后从不在S中的顶点中寻找D数组中的最小值,加入为顶点k,加入S集合。然后更新D信息。D[k] = min{D[k], D[j]+argc[j][k]}后者表示从顶点i到j再到k的路径。重复以上过程n-1次,直到S中包含图中所有顶点为止。
Floyd算法(各顶点之间最短路径,O())
l 时间复杂度为O()
l 算法是一个迭代的过程,允许图中有带负权值的边,但不允许有包含负权值的回路。
l 算法思想:递推产生一个n阶矩阵,,,…,。其中[i][j]表示从顶点i到顶点j,中间顶点的序号不大于k的最短路径长度。从开始,矩阵初始化为就是邻接矩阵,然后进行迭代过程,直到得到,该矩阵就是各顶点之间的最短路径。
拓扑排序(有向图,判断是否有环,O(n+e))
l 时间复杂度为:O(n+e)
l 有向无环图:DAG图,就是有向图没有环
l AOV网:用DAG表示一个工程,顶点表示活动,边<i,j>表示活动i必须先于活动j的关系。
l 拓扑排序:由一个有向无环图的顶点构成的序列。要求有两个:
1. 每个顶点仅出现一次
2. 如果图中有A到B的一条路径,那么拓扑排序中必须是AB的顺序。每个DAG图都有一个或者多个拓扑排序
l 算法思想:从DAG图中找一个入度为0的顶点,然后删除该顶点和以它为起点的所有边,然后再剩下的顶点中继续以上操作,直到没有入度为0的顶点或者图为空。如果是前者说明图中一定有环。
l 深度优先搜索也可以得到拓扑序列。
l 拓扑序列唯一,也不能唯一的确认一个图
关键路径(带权有向图)
l 关键路径的本身是不允许有环的,所以第一步一般都是判断是否有环。
l AOE网:用边表示活动,边的权值表示活动的花销;点表示某个事件。
l 性质:只有某个顶点所代表的事件发生之后,从该顶点出发的有向边所代表的活动才能开始;只有在进入某一顶点的各有向边所代表的活动结束后,该顶点代表的事件才能发生。AOE网中只有一个入度为0【开始顶点/源点】和一个出度为0的顶点【结束顶点/汇点】。
l 关键路径:从源点到汇点的所有路径中,具有最大路径长度的称之为关键路径。完成整个工程的最短时间就是关键路径的长度。
l 关键活动:关键路径上的活动称之为关键活动。如果关键活动不能按时完成的话,整个工期的时间就会延长。
l 事件k发生的最早时间:从前向后推,从源点到事件k顶点的最大路径长度
l 事件k发生的最晚时间:从后向前推,用最早时间减去边上的权重,找的是最小
l 活动i的最早开始时间:<i,j>就是事件i的最早时间
l 活动i的最迟开始时间:<i,j>就是事件j的最晚时间-<i,j>的权值
l 活动i的差值:就是活动i的最迟开始时间-最早开始时间
l 如果活动i的差值为0,说明这个活动就是关键活动。所以算法思想就是:先找出事件和活动的最早和最晚开始时间,然后计算活动的差值,差值为零的就是关键活动,关键活动组成路径上的事件,就是关键事件。
l 注:关键活动决定整个工程的关键,所以可以通过缩短关键活动来加快整个工期。但是也不能随意缩短,因为有可能导致该关键活动变得不是关键活动了;网中的关键路径并不是唯一的,所以缩短某一条关键路径上的关键活动是不能够缩短整个工期的,只有缩短所有关键路径都包含的关键活动才能达到目的。