一:基本概念
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)定义
- 用一个一维数组存储图中n个顶点的信息
- 一个二维数组(称为邻接矩阵)存储图中的n×n条边或弧的信息
- 无权值:如果两点之间有关联则为1,否则为0。
- 有权值:如果两点之间有关联则为w,否则为∞,点与自身之间为0.
(2)特点
- 矩阵的主对角线全为0
- 无向图的矩阵是一个对称矩阵
- 无向图中某个顶点vi的度就是矩阵的第i行或列的元素个数和。
- 有向图中某个顶点vi的入度就是矩阵第i列的元素个数和,出度就是矩阵第i行的元素个数和
- 某个顶点vi的邻接顶点就是矩阵中第i行中值为1/w的顶点
(3)属性
- 顶点数组
- 边或弧的二维数组
- 顶点个数
- 边或弧个数
(4)构建
- 根据顶点个数和顶点信息,初始化一维数组
- 根据边弧个数,初始化二维数组。根据权值和坐标赋值。
(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)定义
- 用一个一维数组存储每个顶点,每个数据元素还需要存储指向第一个邻接点的指针,以便于査找该顶点的边信息。
- 用一个线性表存储每个顶点vi的所有邻接点,由于邻接点的个数不定,所以用单链表存储(无向图称为顶点Vi的边表,有向图则称为顶点Vi作为弧尾的出边表)。
(2)属性
顶点表:
- 数据域:存储结点数据
- 指针域:指向边表的第一个结点
边表:
- 邻接点域:存储某顶点的邻接点在顶点表中的下标
- 指针域:指向边表中下一个结点
- 数据域:对于网图,存储结点的权值
(3)特点
- 无向图中顶点的度就是顶点对应的边表的长度
- 有向图中顶点的出度就是顶点对应的边表的长度
- 顶点的邻接顶点就是顶点对应的边表的所有结点
(4)构建
- 根据顶点个数和顶点信息,初始化顶点表,并将顶点的边表置为空。
- 根据边弧个数初始化边表,根据坐标分别初始化结点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. 十字链表
定义和属性
- 用一个一维数组存储每个顶点,每个数据元素需要存储一个入边表的头指针,和一个出边表的头指针。
- 用一个单链表存储每个顶点vi的所有邻接点,用两个数据域表示弧的起点和终点在顶点表的下标,用两个指针域表示终点相同的下一条边和起点相同的下一条边。还可以用一个数据域表示结点的权值。
特点
十字链表把邻接表和逆邻接表整合在了一起,这样既容易找到以vi为尾的弧,也容易找到以vi为头的弧,因而容易求得顶点的出度和入度。而且它的时间复杂度和邻接表相同。
4. 邻接多重表(适合无向图对边操作)
定义
在十字链表中的边表中,对于(顶点1,顶点2)用两个数据域表示与其依附的顶点1和顶点2在顶点表中下标,用指针域分别指向依附的顶点1的下一条边(顶点1,顶点3)和依附顶点2的下一条边(顶点2,顶点4)在边表的结点。
特点
在边表中只用了一个顶点表示边,若想删除边,只需将原来指向它的指针指向空即可。
5. 边集数组(适合对边操作)
由两个一维数组构成。一个是存储顶点的倌息;另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标、终点下标和权组成。
四:遍历
从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次。
1. 深度遍历(DFS)——树的前序遍历
定义
连通图:它从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。
非连通图:只需对它的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次深度优先遍历后,若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
过程
- 对点集合进行遍历,初始化所有点未被访问。从点V1开始,标记V1。
- 邻接矩阵:对边集合进行遍历,如果存在点V2与点V1关联的边且没有标记,则递归V2。
- 邻接表:从顶点表中V1的边表的头结点V2开始,对边表进行遍历。如果没有标记,则递归V2。
特点
- 所需要的时间与每个顶点的度数成正比
- 需要访问两次边
- 对于点多边少的稀疏图来说,邻接表结构使得算法在时间效率上大大提髙。
- 适合目标明确的情况
2.广度优先遍历(BFS)——树的层序遍历 / 队列
定义
连通图:它从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发广度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。
非连通图:只需对它的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次广度优先遍历后,若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止
过程
- 初始化一个辅助的队列Q
- 对点集合进行遍历,初始化所有点未被访问。从点V1开始,标记V1并加入队列。
- 邻接矩阵:如果队列不为空,则将队头元素出队,对边集合进行遍历,如果存在点V2与点V1关联的边且没有标记,则标记V2并加入队列,继续循环。
- 邻接表:如果队列不为空,则将队头元素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的最小生成树。
过程
- 创建:创建两个数组A B分别保存顶点下标和边的权值。将v0加入生成树,A和B的第一个元素为0
- 初始化:对顶点集合进行遍历,将A初始化为v0的下标,将与v0有关的边的权值加入数组B
- 找最小值:对顶点V1的边集合进行遍历,初始化最小权值min,如果权值不为0,也就是说还未加入的顶点且权值小于min,则赋值最小权值并保存下标i。遍历后获取到与V1有关的边中权值最小的边和顶点Vi,并把结点 Vi 加入到生成树中。
- 更新权值:对顶点Vi的边集合进行遍历,如果权值不为0,也就是说还未加入的顶点且权值小于B中已有的权值,则将较小值保存在数组B中,把下标i保存在数组A中。
- 继续对其他结点遍历
- 最小生成树:由数组A获取,生成树包含边(A[i],i)
(2)克鲁斯卡尔算法(稀疏图)
定义:逐步找最小边
假设N=(V,{E})是连通网,则令最小生成树的初始状态为只有n个顶点而无边的非连通图T={V,{}},图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边而选择下一条代价最小的边。依次类推,直至T中所有顶点都在同一连通分量上为止。
过程
- 初始化:将图转化为边集数组E,并且对它们按权值从小到大排序。创建数组A判断是否成环,初始化为0。
- 判断成环:对图的已排序边集数组E进行遍历。判断边(a,b)与生成树是否会成环:通过数组A找到已加入树的结点a和结点b的连线尾端结点,如果相同,则证明如果加入边(a,b)将会有顶点重复,就会成环,则跳过这条边。
- 找最小边:若不会成环,则将边的尾结点放入下标为起点的A中,表示边已经加入二叉树。
- 最小生成树:由A获取,下标 i 的值 A[i] 表示生成树包含边(i,A[I])
六:最短路径
1. 定义
非网图: 指两顶点之间经过的边数最少的路径。
网图: 指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。
2. 方法
(1)迪杰斯特拉(Dijkstra )算法(一点与所有顶点之间,非负权图)
定义
基于已经求出的最短路径的基础上,求得更远顶点的最短路径。
过程
- 创建:创建两个数组A B分别保存顶点下标和边的权值。
- 初始化:从顶点v0开始,遍历顶点集合,初始化数组C标记所有顶点未找到最短路径,如果有边依附于v0则将边的权值赋值给数组B。
- 获取靠近的顶点:遍历顶点集合,寻找离v0最近的顶点。如果顶点i的权值小于min且顶点i未标记,则更新最小值。遍历后获取最近的顶点vi,对其标记。
- 修正最小值:遍历顶点集合,如果v0经过vi到达未被标记顶点的路径比B中的现在这条直接到达顶点的路径还短,则修正B和A
- 最短路径:v0到vi的最短路径可以看D[i],A[i]表示v0经过A[i]到达vi
(2)弗洛伊德算法(所有顶点与所有顶点之间)
定义
用一个二重循环初始化,用一个三重循环权值修正,就完成了所有顶点到所有顶点的最短路径计算。
流程
- 初始化:创建并初始化一个用来存储权值的矩阵A和一个用来存储路径的矩阵B
- 修正最小值:对于每个顶点再遍历顶点集合,如果经过下标为i的顶点比原来的路径更短,则修改A和B。
- 最短路径: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的顶点为止。
过程:
- 创建并初始化一个栈,用于存储入度为0的顶点
- 对栈进行遍历,将元素v弹出栈并输出。
- 对v的边表进行遍历,并把边表的结点vi的入度-1(也就是删除结点v为尾的弧),若入度为0则入栈
- 如果输出的结点等于图的总结点数,则存在环。
八:关键路径
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的过程,就是我们从头至尾找拓扑序列的过程
- 创建两个数组A和B,用于保存事件最早发生时间和最迟发生时间,初始化A为0。创建一个栈C存储入度为0的顶点,创建一个栈D,用于保存序列。
- 对栈C进行遍历,将元素v弹出栈并压入栈D。
- 对v的边表进行遍历,并把边表的结点vi的入度-1(也就是删除结点v为尾的弧),若入度为0则入栈C。如果经过结点vi的路径的发生时间比原先的最早发生时间更大,则更新最早发生时间A。(可以晚点的开始活动)
再求最短路径:
- 声明活动最早发生时间和最晚发生时间,初始化B为最早发生时间的最大值
- 对栈D进行遍历,将元素v弹出栈
- 对v的边表进行遍历,如果不要经过结点vi的路径的发生时间比原先的最晚发生时间更小,则更新最晚发生时间B。(必须要更早的完成活动)
- 对于每个顶点vi的边表进行遍历vj,最早发生时间为vi的最早时间,最晚发生时间为vj的最晚时间再减去经过此顶点权值,如果vi和vj相同,则在关键路径上