图
图
图的定义
注意:
- 顶点,我们在树的定义中将数据元素称为结点,在图中我们定义为顶点
- 在图结构中,至少存在一个顶点
- 图中,任意两个顶点之间都可能存在关系,顶点之间的逻辑关系用边来表示
图的基本概念
无向图和有向图
图的度
连通图
生成树和有向树
图的存储方式
邻接矩阵
网的邻接矩阵
网:每条边有权值的图
邻接表
使用数组存储顶点信息,使用链表存储边的信息
网的邻接表
十字链表
邻接多重表
邻接多重表和邻接表的差别:同一条边,在邻接表中使用两个结点表示,在邻接多重表中使用一个结点表示
边集数组
图的遍历
图的遍历:从图中的某一个顶点出发访问图中的其余顶点,且使每一个顶点仅被访问一次,这一过程叫做图的遍历
深度优先遍历(DFS)
递归实现DFS
// 深度优先遍历(从head结点开始) 递归算法 邻接表
void DFS1(Node head) {
Node pNode;
if (!this.nodes[head.name].flag) {
System.out.print(head.name + " ");
this.nodes[head.name].flag = true;
}
pNode = this.nodes[head.name].next;
while (pNode != null) {
if (!this.nodes[pNode.name].flag) {
DFS1(pNode);
} else {
pNode = pNode.next;
}
}
}
非递归实现DFS
非递归可以使用栈来实现DFS
1. 初始化栈
2. 输出起始的顶点,起始顶点改为“已访问”的标志,将起始顶点进栈重复一下的操作直至栈为空;
3. 取栈顶元素顶点,存在着未被访问的邻结点W输出顶点W,将顶点W改为“已访问”,将W进栈;否则,当前顶点退栈
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
import java.util.*;
import java.util.Stack;
import java.util.ArrayList;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
// write code here
Stack<TreeNode> st1 = new Stack<TreeNode>();
int isLeft=0;
st1.push(t1);
System.out.println(t1.val);
while (st1.size()!=0){
TreeNode pointer=st1.peek();
if(isLeft==0){
if(pointer.left!=null){
System.out.println(pointer.left.val);
st1.push(pointer.left);
isLeft=0;
}else
isLeft=1;
}
else{
st1.pop();
isLeft=1;
if(pointer.right!=null){
System.out.println(pointer.right.val);
st1.push(pointer.right);
isLeft=0;
}
}
}
return t1;
}
}
广度优先遍历
递归实现BFS
void BFS1(Node head) {
// 初始化访问标记
initFlag();
// 初始化队列
this.queue.initQueue(20);
Node pNode;
this.queue.enterQueue(head);
this.nodes[head.name].flag = true;
while (!this.queue.isEmpty()) {
pNode = this.queue.deleteQueue();
pNode = this.nodes[pNode.name];
System.out.print(pNode.name + " ");
while (pNode != null) {
pNode = pNode.next;
if (pNode != null && !this.nodes[pNode.name].flag) {
this.queue.enterQueue(pNode);
this.nodes[pNode.name].flag = true;
}
}
}
}
非递归实现BFS
非递归可以使用队列来实现BFS
package com.rf.springboot01.dataStructure.graph;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
/**
* @description: 定义一个图 类
* @author: xiaozhi
* @create: 2020-10-28 21:11
*/
public class GraphBFS {
private ArrayList<String> vertexList;//存储顶点集合
private int[][] edges;//存储图所对应的邻接矩阵
private int numEdges;//表示边的数量
private boolean[] isVisited;//定义一个boolean类型的数组,记录某个节点是否被访问
//构造方法 n表示有多少个顶点
public GraphBFS(int n){
//初始化
vertexList=new ArrayList<>(n);
edges =new int[n][n];
numEdges=0;
isVisited=new boolean[5];
}
/**
* @Description: 得到第一个邻接结点的下标 w
* @param index
* @Author: xz
* @return: 如果存在就返回对应的下标,否则返回-1
* @Date: 2020/10/28 22:27
*/
public int getFirstNodeIndex(int index){
for(int j=0;j<vertexList.size();j++){
if(edges[index][j] > 0){
return j;
}
}
return -1;
}
/**
* @Description: 根据前一个邻接结点的下标来获取下一个邻接结点
* @Author: xz
* @return: 如果存在就返回对应的下标,否则返回-1
* @Date: 2020/10/28 22:38
*/
public int getNextNodeIndex(int v1,int v2){
for(int j=v2+1; j<vertexList.size();j++){
if(edges[v1][j] > 0){
return j;
}
}
return -1;
}
/**
* @Description: 广度优先遍历的方法
* @param i 第一次就是 0
* isVisited 某个节点是否被访问
* @Author: xz
* @Date: 2020/10/28 22:20
*/
private void bfsMethods(boolean[] isVisited, int i) {
int u ; // 表示队列的头结点对应下标
int w ; // 邻接结点w
//队列,记录结点访问的顺序
LinkedList queue = new LinkedList();
//访问结点,输出结点信息
System.out.print(getValueByIndex(i) + "=>");
//标记为已访问
isVisited[i] = true;
//将结点加入队列
queue.addLast(i);
while( !queue.isEmpty()) {
//取出队列的头结点下标
u = (Integer)queue.removeFirst();
//得到第一个邻接结点的下标 w
w = getFirstNodeIndex(u);
while(w != -1) {//找到
//是否访问过
if(!isVisited[w]) {
System.out.print(getValueByIndex(w) + "=>");
//标记已经访问
isVisited[w] = true;
//入队
queue.addLast(w);
}
//以u为前驱点,找w后面的下一个邻结点
w = getNextNodeIndex(u, w); //体现出我们的广度优先
}
}
}
/**
* @Description: 重载bfsMethods方法
* @Author: xz
* @Date: 2020/10/28 22:47
*/
public void bfsMethods() {
isVisited = new boolean[vertexList.size()];
for(int i = 0; i < getNumVertex(); i++) {
if(!isVisited[i]) {
bfsMethods(isVisited, i);
}
}
}
/**
* @Description: 新增节点方法
* @Param: vertex 节点
* @Author: xz
* @Date: 2020/10/28 22:02
*/
public void insertVertex(String vertex){
vertexList.add(vertex);
}
/**
* @Description: 新增边的方法
* @Param: v1 表示点对应的的下标,即第几个顶点 ,比如 A — B, 点A的下标为0,点B的下标为1
* v2,表示第二个顶点对应的下标
* @Author: xz
* @return: void
* @Date: 2020/10/28 22:02
*/
public void insertEdge(int v1 ,int v2 ,int weight) {
edges[v1][v2] =weight;
edges[v2][v1]=weight;
numEdges++;
}
/**
* @Description: 返回结点个数的方法
* @Author: xz
* @Date: 2020/10/28 22:25
*/
public int getNumVertex(){
return vertexList.size();
}
/**
* @Description: 返回边的数量方法
* @Author: xz
* @Date: 2020/10/28 22:26
*/
public int getNumEdge(){
return numEdges;
}
/**
* @Description: 返回节点i(下标)对应的数据 0->"A" 1->"B" 2->"C"
* @Author: xz
* @Date: 2020/10/28 22:31
*/
public String getValueByIndex(int i){
return vertexList.get(i);
}
/**
* @Description: 返回v1和v2的权值
* v1 表示点对应的的下标
* v2 表示第二个顶点对应的下标
* @Author: xz
* @Date: 2020/10/28 22:34
*/
public int getWeight(int v1, int v2) {
return edges[v1][v2];
}
/**
* @Description: 显示图对应的矩阵
* @Author: xz
* @Date: 2020/10/28 22:44
*/
public void showGraph(){
for(int[] link : edges) {
System.err.println(Arrays.toString(link));
}
}
}
最小生成树
普里姆算法(prime)
理解:该算法的简单思路为,先选取任意一个点作为初始点,加入到集合V,
然后选取该点最近的一个点,作为第二个点,此时将第二个点也加入V,
再从V中的所有点的各个边选出一条最短的到达还不在V中点(假设该点为x
点)的边,然后将这个x点加入V,重复上诉过程,直到所有的点都在V中
package com.rf.springboot01.Algorithm.prim;
import java.util.Arrays;
/**
* @description: 普里姆算法(Prim算法)示例
* @author: xz
* @create: 2020-11-13 20:47
*/
public class PrimAlgorithm {
public static void main(String[] args) {
//定义图的各个顶点的值
char[] data=new char[]{'A','B','C','D','E','F','G'};
//根据图的各个顶点的值,获取图对应的顶点个数
int verxs=data.length;
//使用二维数组表示邻接矩阵的关系 ,10000:表示两个点不连通
int[][] weight=new int[][]{
{10000,5,7,10000,10000,10000,2},
{5,10000,10000,9,10000,10000,3},
{7,10000,10000,10000,8,10000,10000},
{10000,9,10000,10000,10000,4,10000},
{10000,10000,8,10000,10000,5,4},
{10000,10000,10000,4,5,10000,6},
{2,3,10000,10000,4,6,10000}
};
//创建Graph对象
Graph graph = new Graph(verxs);
//创建MinTree对象
MinTree minTree=new MinTree();
//创建图的邻接矩阵
minTree.createGraph(graph,verxs,data,weight);
//显示图的邻接矩阵
System.out.println("图的邻接矩阵----------------------");
minTree.showGraph(graph);
//测试普里姆算法
System.out.println("普里姆算法==============");
minTree.prim(graph,0);
}
}
/**
* @Description: 创建最小生成树->村庄的图
* @Author: xz
* @Date: 2020/11/13 21:53
*/
class MinTree{
/**
* @Description: 创建图的邻接矩阵
* @Param: graph 图对象
* verxs 图对应的顶点个数
* data 图的各个顶点的值
* weight 图的邻接矩阵
* @Author: xz
* @Date: 2020/11/13 21:56
*/
public void createGraph(Graph graph, int verxs, char[] data, int[][] weight){
for(int i=0;i<verxs;i++){
graph.data[i]=data[i];
for(int j=0;j<verxs;j++){
graph.weight[i][j]=weight[i][j];
}
}
}
//显示图的邻接矩阵
public void showGraph(Graph graph) {
for(int[] link: graph.weight) {
System.out.println(Arrays.toString(link));
}
}
/**
* @Description: prim算法,得到最小生成树
* @Param: graph 图
* v 表示从图的第几个顶点开始生成'A'->0 'B'->1...
* @Author: xz
* @Date: 2020/11/13 22:08
*/
public void prim(Graph graph, int v) {
//visited[] 标记结点(顶点)是否被访问过,visited[] 默认元素的值都是0, 表示没有访问过
int visited[] = new int[graph.verxs];
//把当前这个结点标记为已访问
visited[v] = 1;
//h1 和 h2 记录两个顶点的下标
int h1 = -1;
int h2 = -1;
int minWeight = 10000; //将 minWeight 初始成一个大数,后面在遍历过程中,会被替换
int sumMinWeight=0;//所有对应边的最小权值的总和
for(int k = 1; k < graph.verxs; k++) {//因为有 graph.verxs顶点,普利姆算法结束后,有 graph.verxs-1边
//这个是确定每一次生成的子图 ,和哪个结点的距离最近
for(int i = 0; i < graph.verxs; i++) {// i结点表示被访问过的结点
for(int j = 0; j< graph.verxs;j++) {//j结点表示还没有访问过的结点
if(visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
//替换minWeight(寻找已经访问过的结点和未访问过的结点间的权值最小的边)
minWeight = graph.weight[i][j];
h1 = i;
h2 = j;
}
}
}
//退出双重for循环后就找到一条边是最小
System.out.println("对应的边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight);
sumMinWeight += minWeight;
//将当前这个结点标记为已经访问
visited[h2] = 1;
//minWeight 重新设置为最大值 10000
minWeight = 10000;
}
System.out.println("所有对应边的最小权值的总和="+sumMinWeight);
}
}
/**
* @Description: 创建图
* @Author: xz
* @Date: 2020/11/13 21:50
*/
class Graph{
int verxs;//图的节点个数
char[] data;//存放图节点的数据
int[][] weight; //存放边,表示邻接矩阵
//构造函数
public Graph(int verxs){
this.verxs=verxs;
data=new char[verxs];
weight=new int[verxs][verxs];
}
}
克鲁斯卡尔算法
理解:先把整个图的所有边构建一个数组,从其中选出 最短的 并且这个边加入到
图中不会产生环,而且该边的加入可以产生新的连通分量(我的理解是,可
以使得图中的某两个点A,B可以通过该边E连接起来了,如果没有添加这个
边E连不起来。同样的,也可以有别的边F可以使得A,B连接,但是E是比
F短)
package com.liuzhen.systemExe;
import java.util.ArrayList;
import java.util.Scanner;
public class Kruskal {
//内部类,其对象表示连通图中一条边
class edge {
public int a; // 开始顶点
public int b; //结束顶点
public int value; //权值
edge(int a, int b, int value) {
this.a = a;
this.b = b;
this.value = value;
}
}
//使用合并排序,把数组A按照其value值进行从小到大排序
public void edgeSort(edge[] A){
if(A.length > 1) {
edge[] leftA = getHalfEdge(A, 0);
edge[] rightA = getHalfEdge(A, 1);
edgeSort(leftA);
edgeSort(rightA);
mergeEdgeArray(A, leftA, rightA);
}
}
//judge = 0返回数组A的左半边元素,否则返回右半边元素
public edge[] getHalfEdge(edge[] A, int judge) {
edge[] half;
if(judge == 0) {
half = new edge[A.length / 2];
for(int i = 0;i < A.length / 2;i++)
half[i] = A[i];
} else {
half = new edge[A.length - A.length / 2];
for(int i = 0;i < A.length - A.length / 2;i++)
half[i] = A[A.length / 2 + i];
}
return half;
}
//合并leftA和rightA,并按照从小到大顺序排列
public void mergeEdgeArray(edge[] A, edge[] leftA, edge[] rightA) {
int i = 0;
int j = 0;
int len = 0;
while(i < leftA.length && j < rightA.length) {
if(leftA[i].value < rightA[j].value) {
A[len++] = leftA[i++];
} else {
A[len++] = rightA[j++];
}
}
while(i < leftA.length) A[len++] = leftA[i++];
while(j < rightA.length) A[len++] = rightA[j++];
}
//获取节点a的根节点编号
public int find(int[] id, int a) {
int i, root, k;
root = a;
while(id[root] >= 0) root = id[root]; //此处,若id[root] >= 0,说明此时的a不是根节点,因为唯有根节点的值小于0
k = a;
while(k != root) { //将a节点所在树的所有节点,都变成root的直接子节点
i = id[k];
id[k] = root;
k = i;
}
return root;
}
//判断顶点a和顶点b的根节点大小,根节点值越小,代表其对应树的节点越多,将节点少的树的节点添加到节点多的树上
public void union(int[] id, int a, int b) {
int ida = find(id, a); //得到顶点a的根节点
int idb = find(id, b); //得到顶点b的根节点
int num = id[ida] + id[idb]; //由于根节点值必定小于0,此处num值必定小于零
if(id[ida] < id[idb]) {
id[idb] = ida; //将顶点b作为顶点a根节点的直接子节点
id[ida] = num; //更新根节点的id值
} else {
id[ida] = idb; //将顶点a作为顶点b根节点的直接子节点
id[idb] = num; //更新根节点的id值
}
}
//获取图A的最小生成树
public ArrayList<edge> getMinSpanTree(int n, edge[] A) {
ArrayList<edge> list = new ArrayList<edge>();
int[] id = new int[n];
for(int i = 0;i < n;i++)
id[i] = -1; //初始化id(x),令所有顶点的id值为-1,即表示为根节点
edgeSort(A); //使用合并排序,对于图中所有边权值进行从小到大排序
int count = 0;
for(int i = 0;i < A.length;i++) {
int a = A[i].a;
int b = A[i].b;
int ida = find(id, a - 1);
int idb = find(id, b - 1);
if(ida != idb) {
list.add(A[i]);
count++;
union(id, a - 1, b - 1);
}
//输出每一次添加完一条边后的所有顶点id值
for(int j = 0;j < id.length;j++)
System.out.print(id[j]+" ");
System.out.println();
if(count >= n - 1)
break;
}
return list;
}
public static void main(String[] args){
Kruskal test = new Kruskal();
Scanner in = new Scanner(System.in);
System.out.println("请输入顶点数a和具体边数p:");
int n = in.nextInt();
int p = in.nextInt();
edge[] A = new edge[p];
System.out.println("请依次输入具体边对于的顶点和权值:");
for(int i = 0;i < p;i++) {
int a = in.nextInt();
int b = in.nextInt();
int value = in.nextInt();
A[i] = test.new edge(a, b, value);
}
ArrayList<edge> list = test.getMinSpanTree(n, A);
System.out.println("使用Kruskal算法得到的最小生成树具体边和权值分别为:");
for(int i = 0;i < list.size();i++) {
System.out.println(list.get(i).a+"——>"+list.get(i).b+", "+list.get(i).value);
}
}
}
最短路径
迪杰斯特拉(Dijkstra)算法
可参考文章:图解最短路径之迪杰斯特拉算法(Java实现)
这是一个按照路径长度依次递增的次序寻找最短路径的方法。
个人理解:假设需要求一个从A到E的最短路径,那就先找从A到它的邻近点的的最短路径,假设A到B是1,
A到C是3,那么我们知道A到B的最短距离是1,下一个循环,我们去看B到它的邻近点的路径,可以得到A到
这些点的路径,再选出一个最短的路径,假设,B到C是1,B到D是2,那么A到C的最短路径就变成A->B->C
是2,那么我们选择C作为下一轮循环的点,重复上面的过程,可以获得A到所有点的最短路径。
public class DijstraAlgorithm {
//不能设置为Integer.MAX_VALUE,否则两个Integer.MAX_VALUE相加会溢出导致出现负权
public static int MaxValue = 100000;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入顶点数和边数:");
//顶点数
int vertex = input.nextInt();
//边数
int edge = input.nextInt();
int[][] matrix = new int[vertex][vertex];
//初始化邻接矩阵
for (int i = 0; i < vertex; i++) {
for (int j = 0; j < vertex; j++) {
matrix[i][j] = MaxValue;
}
}
for (int i = 0; i < edge; i++) {
System.out.println("请输入第" + (i + 1) + "条边与其权值:");
int source = input.nextInt();
int target = input.nextInt();
int weight = input.nextInt();
matrix[source][target] = weight;
}
//单源最短路径,源点
int source = input.nextInt();
//调用dijstra算法计算最短路径
dijstra(matrix, source);
}
public static void dijstra(int[][] matrix, int source) {
//最短路径长度
int[] shortest = new int[matrix.length];
//判断该点的最短路径是否求出
int[] visited = new int[matrix.length];
//存储输出路径
String[] path = new String[matrix.length];
//初始化输出路径
for (int i = 0; i < matrix.length; i++) {
path[i] = new String(source + "->" + i);
}
//初始化源节点
shortest[source] = 0;
visited[source] = 1;
for (int i = 1; i < matrix.length; i++) {
int min = Integer.MAX_VALUE;
int index = -1;
for (int j = 0; j < matrix.length; j++) {
//已经求出最短路径的节点不需要再加入计算并判断加入节点后是否存在更短路径
if (visited[j] == 0 && matrix[source][j] < min) {
min = matrix[source][j];
index = j;
}
}
//更新最短路径
shortest[index] = min;
visited[index] = 1;
//更新从index跳到其它节点的较短路径
for (int m = 0; m < matrix.length; m++) {
if (visited[m] == 0 && matrix[source][index] + matrix[index][m] < matrix[source][m]) {
matrix[source][m] = matrix[source][index] + matrix[index][m];
path[m] = path[index] + "->" + m;
}
}
}
//打印最短路径
for (int i = 0; i < matrix.length; i++) {
if (i != source) {
if (shortest[i] == MaxValue) {
System.out.println(source + "到" + i + "不可达");
} else {
System.out.println(source + "到" + i + "的最短路径为:" + path[i] + ",最短距离是:" + shortest[i]);
}
}
}
}
}
弗洛伊德(Floyd)算法
参考文献:弗洛伊德(Floyd)算法
个人理解:先构建两个矩阵D、P,D代表当前图的初始邻接矩阵,D的每一行的初始元素为当前点到它的对应
邻近点的距离,非邻近点的元素为无穷大;P代表的是对应顶点的最短路径的前驱矩阵;初始化完毕以后,
假设我们现在需要找A到E的最短路径,那么我们可以假设所有的顶点都需要从B点经过去找到到其他点的
最短路径,一个循环以后,我们可以找到A—>B->某个点的最短路径,重复上述过程,不断更新矩阵,直到
找到A到所有点的最短路径。
import java.util.Arrays;
public class FloydAlgorithm {
public static void main(String[] args) {
//创建顶点
char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
//创建邻接矩阵
int[][] matrix = new int[vertex.length][vertex.length];
final short N = Short.MAX_VALUE;
matrix[0] = new int[]{0, 5, 7, N, N, N, 2};
matrix[1] = new int[]{5, 0, N, 9, N, N, 3};
matrix[2] = new int[]{7, N, 0, N, 8, N, N};
matrix[3] = new int[]{N, 9, N, 0, N, 4, N};
matrix[4] = new int[]{N, N, 8, N, 0, 5, 4};
matrix[5] = new int[]{N, N, N, 4, 5, 0, 6};
matrix[6] = new int[]{2, 3, N, N, 4, 6, 0};
//创建graph对象
Graph graph = new Graph(matrix,vertex);
//调用弗洛伊德算法
graph.floyd();
graph.show();
}
}
/**
* 创建图
*/
class Graph {
/**
* 存放顶点的数组
*/
private char[] vertes;
/**
* 保存从各个顶点触发到其他顶点的距离,最后的结果也是保留在该数组
*/
private int[][] dis;
/**
* 保存到达目标顶点的前驱顶点
*/
private int[][] pre;
public Graph(int[][] matrix, char[] vertex) {
this.vertes = vertex;
this.dis = matrix;
this.pre = new int[vertex.length][vertex.length];
//对pre数组初始化,注意存放的是前驱顶点的下标
for (int i = 0; i < vertex.length; i++) {
Arrays.fill(pre[i], i);
}
}
/**
* 显示pre数组和dis数组
*/
public void show() {
//为了显示便于阅读
for (int k = 0; k < dis.length; k++) {
//先将pre数组输出到一行
for (int i = 0; i < dis.length; i++) {
System.out.print(vertes[pre[k][i]] + " ");
}
System.out.println();
//输出dis数组的一行数据
for (int i = 0; i < dis.length; i++) {
System.out.println(" <" + vertes[k] + "," + vertes[i] + "> => " + dis[k][i]);
}
System.out.println();
}
}
/**
* 弗洛伊德算法
*/
public void floyd() {
//保存距离
int len;
//对中间顶点遍历
for (int k = 0; k < dis.length; k++) {
//从i顶点开始出发
for (int i = 0; i < dis.length; i++) {
//到达j顶点
for (int j = 0; j < dis.length; j++) {
//求出从i顶点出发经过k到达j的距离
len = dis[i][k] + dis[k][j];
//若len小于dis[i][j],则进行更新
if (len < dis[i][j]) {
//更新距离
dis[i][j] = len;
//更新前驱顶点
pre[i][j] = pre[k][j];
}
}
}
}
}
}
图的拓扑排序
参考文献:拓扑排序
确定一系列活动的先后顺序。
基于减治的思想实现拓扑排序。
package com.liuzhen.chapterFour;
import java.util.Stack;
public class TopologicalSorting {
//方法1:基于减治法:寻找图中入度为0的顶点作为即将遍历的顶点,遍历完后,将此顶点从图中删除
/*
* 参数adjMatrix:给出图的邻接矩阵值
* 参数source:给出图的每个顶点的入度值
* 该函数功能:返回给出图的拓扑排序序列
*/
public char[] getSourceSort(int[][] adjMatrix,int[] source){
int len = source.length; //给出图的顶点个数
char[] result = new char[len]; //定义最终返回路径字符数组
int count = 0; //用于计算当前遍历的顶点个数
boolean judge = true;
while(judge){
for(int i = 0;i < source.length;i++){
if(source[i] == 0){ //当第i个顶点入度为0时,遍历该顶点
result[count++] = (char) ('a'+i);
source[i] = -1; //代表第i个顶点已被遍历
for(int j = 0;j < adjMatrix[0].length;j++){ //寻找第i个顶点的出度顶点
if(adjMatrix[i][j] == 1)
source[j] -= 1; //第j个顶点的入度减1
}
}
}
if(count == len)
judge = false;
}
return result;
}
/*
* 参数adjMatrix:给出图的邻接矩阵值
* 函数功能:返回给出图每个顶点的入度值
*/
public int[] getSource(int[][] adjMatrix){
int len = adjMatrix[0].length;
int[] source = new int[len];
for(int i = 0;i < len;i++){
//若邻接矩阵中第i列含有m个1,则在该列的节点就包含m个入度,即source[i] = m
int count = 0;
for(int j = 0;j < len;j++){
if(adjMatrix[j][i] == 1)
count++;
}
source[i] = count;
}
return source;
}
public static void main(String[] args){
TopologicalSorting test = new TopologicalSorting();
int[][] adjMatrix = {{0,0,1,0,0},{0,0,1,0,0},{0,0,0,1,1},{0,0,0,0,1},{0,0,0,0,0}};
int[] source = test.getSource(adjMatrix);
System.out.println("给出图的所有节点(按照字母顺序排列)的入度值:");
for(int i = 0;i < source.length;i++)
System.out.print(source[i]+"\t");
System.out.println();
char[] result = test.getSourceSort(adjMatrix, source);
System.out.println("给出图的拓扑排序结果:");
for(int i = 0;i < result.length;i++)
System.out.print(result[i]+"\t");
}
}
关键路径
参考文献:关键路径
找到一系列活动的关键活动,这些关键活动耗费的时间是整个系列活动耗费的总时间。
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* AOE网(Activity On Edge Network):<p>
* 在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间。<p>
* 注:与AOV网不同的是,AOV网是顶点表示活动的网,它值描述活动之间的制约关系,而AOE网是
* 用边表示活动的网,边上的权值表示活动持续的时间。
*/
public class CriticalPath {
/**
* 顶点数组
*/
private List<VertexNode> vexList;
/**
* etv:earliest time of vertex 事件的最早发生时间<p>
* ltv:latest time of vertex 事件的最晚发生时间
*/
private int etv[], ltv[];
/**
* 拓扑序列,保存各顶点拓扑排序的顺序
*/
private Stack<Integer> stack2;
/**
* 创建图1(邻接表)
*/
public void createGraph1() {
//v0
VertexNode v0 = new VertexNode(0, 0, null);
EdgeNode v0e0 = new EdgeNode(3, 3, null);
v0.setFirstEdge(v0e0);
//v1
VertexNode v1 = new VertexNode(0, 1, null);
EdgeNode v1e0 = new EdgeNode(3, 1, null);
EdgeNode v1e1 = new EdgeNode(4, 2, null);
v1.setFirstEdge(v1e0);
v1e0.setNext(v1e1);
//v2
VertexNode v2 = new VertexNode(0, 2, null);
EdgeNode v2e0 = new EdgeNode(4, 3, null);
v2.setFirstEdge(v2e0);
//v3
VertexNode v3 = new VertexNode(2, 3, null);
EdgeNode v3e0 = new EdgeNode(5, 1, null);
v3.setFirstEdge(v3e0);
//v4
VertexNode v4 = new VertexNode(2, 4, null);
EdgeNode v4e0 = new EdgeNode(5, 1, null);
v4.setFirstEdge(v4e0);
//v5
VertexNode v5 = new VertexNode(2, 5, null);
vexList = new ArrayList<>();
vexList.add(v0);
vexList.add(v1);
vexList.add(v2);
vexList.add(v3);
vexList.add(v4);
vexList.add(v5);
}
/**
* 创建图2(邻接表)
*/
public void createGraph2() {
//v0
VertexNode v0 = new VertexNode(0, 0, null);
EdgeNode v0e0 = new EdgeNode(2, 4, null);
EdgeNode v0e1 = new EdgeNode(1, 3, null);
v0.setFirstEdge(v0e0);
v0e0.setNext(v0e1);
//v1
VertexNode v1 = new VertexNode(1, 1, null);
EdgeNode v1e0 = new EdgeNode(4, 6, null);
EdgeNode v1e1 = new EdgeNode(3, 5, null);
v1.setFirstEdge(v1e0);
v1e0.setNext(v1e1);
//v2
VertexNode v2 = new VertexNode(1, 2, null);
EdgeNode v2e0 = new EdgeNode(5, 7, null);
EdgeNode v2e1 = new EdgeNode(3, 8, null);
v2.setFirstEdge(v2e0);
v2e0.setNext(v2e1);
//v3
VertexNode v3 = new VertexNode(2, 3, null);
EdgeNode v3e0 = new EdgeNode(4, 3, null);
v3.setFirstEdge(v3e0);
//v4
VertexNode v4 = new VertexNode(2, 4, null);
EdgeNode v4e0 = new EdgeNode(7, 4, null);
EdgeNode v4e1 = new EdgeNode(6, 9, null);
v4.setFirstEdge(v4e0);
v4e0.setNext(v4e1);
//v5
VertexNode v5 = new VertexNode(1, 5, null);
EdgeNode v5e0 = new EdgeNode(7, 6, null);
v5.setFirstEdge(v5e0);
//v6
VertexNode v6 = new VertexNode(1, 6, null);
EdgeNode v6e0 = new EdgeNode(9, 2, null);
v6.setFirstEdge(v6e0);
//v7
VertexNode v7 = new VertexNode(2, 7, null);
EdgeNode v7e0 = new EdgeNode(8, 5, null);
v7.setFirstEdge(v7e0);
//v8
VertexNode v8 = new VertexNode(1, 8, null);
EdgeNode v8e0 = new EdgeNode(9, 3, null);
v8.setFirstEdge(v8e0);
//v9
VertexNode v9 = new VertexNode(2, 9, null);
vexList = new ArrayList<>();
vexList.add(v0);
vexList.add(v1);
vexList.add(v2);
vexList.add(v3);
vexList.add(v4);
vexList.add(v5);
vexList.add(v6);
vexList.add(v7);
vexList.add(v8);
vexList.add(v9);
}
/**
* 拓扑排序 用于关键路径计算<p>
*
* 新加了一些代码
*/
public boolean topologicalSort() {
//统计输出顶点数
int count = 0;
//建栈存储入度为0的顶点
Stack<Integer> stack = new Stack<>();
//统计入度数
for (int i = 0;i < vexList.size(); i++) {
vexList.get(i).setIn(0);
}
for (int i = 0;i < vexList.size(); i++) {
EdgeNode edge = vexList.get(i).getFirstEdge();
while (edge != null) {
VertexNode vex = vexList.get(edge.getAdjvex());
vex.setIn(vex.getIn() + 1);
edge = edge.getNext();
}
}
//将入度为0 的顶点入栈
for (int i = 0;i < vexList.size(); i++) {
if (vexList.get(i).getIn() == 0) {
stack.push(i);
}
}
//----新加begin---- 初始化
etv = new int[vexList.size()];
stack2 = new Stack<>();
//----新加end----
System.out.print("拓扑序列:");
while (!stack.isEmpty()) {
//栈顶 顶点出栈
int vexIndex = stack.pop();
System.out.print(vexIndex + " ");
count++;
//----新加 。将弹出的顶点序号压入拓扑序列的栈
stack2.push(vexIndex);
EdgeNode edge = null;
//----循环方式变了一下
for (edge = vexList.get(vexIndex).getFirstEdge(); edge != null; edge = edge.getNext()){
int adjvex = edge.getAdjvex();
VertexNode vex = vexList.get(adjvex);
//将此 顶点的入度减一
vex.setIn(vex.getIn() - 1);
//此顶点的入度为零则入栈,以便于下次循环输出
if (vex.getIn() == 0) {
stack.push(adjvex);
}
//----新加 求各顶点的最早发生时间值。
if (etv[vexIndex] + edge.getWeight() > etv[adjvex]) {
etv[adjvex] = etv[vexIndex] + edge.getWeight();
}
}
}
if (count != vexList.size())
return false;
else
return true;
}
/**
* 关键路径
*/
public void criticalPath() {
//求拓扑序列,计算数组etv和stack2的值
boolean success = topologicalSort();
if (!success) {
System.out.println("\n有回路");
return;
}
//声明活动最早发生时间和最迟发生时间
int ete, lte;
//初始化ltv
ltv = new int[vexList.size()];
for (int i = 0; i <vexList.size(); i++)
ltv[i] = etv[vexList.size() - 1];
System.out.print("\n关键路径:\n");
//求顶点的最晚发生时间
while (!stack2.isEmpty()) {
//将拓扑序列出栈
int vexIndex = stack2.pop();
EdgeNode edge = null;
for (edge = vexList.get(vexIndex).getFirstEdge();
edge != null; edge = edge.getNext()) {
int adjvex = edge.getAdjvex();
//求各顶点最晚发生时间
//已知最早发生时间,才能求最晚发生时间,顺序不能倒过来
//最晚完成时间要按拓扑序列逆推出来
//个人理解:求最晚和最早原理相同,只不过是返回来
if (ltv[adjvex] - edge.getWeight() < ltv[vexIndex]) {
ltv[vexIndex] = ltv[adjvex] - edge.getWeight();
}
}
}
for (int i = 0; i < vexList.size(); i++) {
EdgeNode edge = null;
for (edge = vexList.get(i).getFirstEdge(); edge != null; edge = edge.getNext()) {
int adjvex = edge.getAdjvex();
//活动最早发生时间,即为边的弧头的最早发生时间
ete = etv[i];
//活动最晚发生时间,即为弧尾的的最晚发生时间减去权值
lte = ltv[adjvex] - edge.getWeight();
//相等即为关键路径
if (ete == lte) {
System.out.printf("<v%d,v%d>: %d\n",
vexList.get(i).getData(), vexList.get(adjvex).getData(), edge.getWeight());
}
}
}
}
public static void main(String[] args) {
CriticalPath criticalPath = new CriticalPath();
criticalPath.createGraph1();
//criticalPath.createGraph2();
criticalPath.criticalPath();
}
}
/**
* 边表结点
*
*/
class EdgeNode {
/**
* 邻接点域,存储该顶点对应的下标
*/
private int adjvex;
/**
* 用于存储权值,对于非网图可以不需要
*/
private int weight;
/**
* 链域,指向下一个邻接点
*/
private EdgeNode next;
public EdgeNode(int adjvex, int weight, EdgeNode next) {
super();
this.adjvex = adjvex;
this.weight = weight;
this.next = next;
}
public int getAdjvex() {
return adjvex;
}
public void setAdjvex(int adjvex) {
this.adjvex = adjvex;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public EdgeNode getNext() {
return next;
}
public void setNext(EdgeNode next) {
this.next = next;
}
}
/**
* 顶点表结点
*
*/
class VertexNode {
/**
* 顶点入度
*/
private int in;
/**
* 顶点域,存储顶点信息(下标)
*/
private int data;
/**
* 边表头指针
*/
private EdgeNode firstEdge;
public VertexNode(int in, int data, EdgeNode firstEdge) {
super();
this.in = in;
this.data = data;
this.firstEdge = firstEdge;
}
public int getIn() {
return in;
}
public void setIn(int in) {
this.in = in;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public EdgeNode getFirstEdge() {
return firstEdge;
}
public void setFirstEdge(EdgeNode firstEdge) {
this.firstEdge = firstEdge;
}
}