学习来源:日撸 Java 三百行(31-40天,图)_闵帆的博客——CSDN博客
一、邻接表
当一个图为稀疏图时,使用邻接矩阵会浪费大量的存储空间,图的邻接表存储方式结合了顺序存储和链式存储方法,节省了存储空间。对于图中的每个顶点建立一个单链表,链表中的节点表示依附于顶点的边。对于所有顶点采用顺序存储。
package JavDay8;
import JavaDay5.CircleObjectQueue;
import JavaDay7.Graph;
/**
* @author Kexiong Wang
*
* @date 2022年4月25日
*
* 邻接表
*/
public class AdjacencyList {
//邻接表节点
class AdjacencyNode{
//顶点号
int column;
//指针域
AdjacencyNode next;
/**
*********************
* 构造函数
*
* @param paraColumn 顶点号
*********************
*/
public AdjacencyNode(int paraColumn) {
column = paraColumn;
next = null;
}//Of AdjacencyNode
}//Of class AdjacencyNode
//图的顶点数
int nodeNums;
//每一行邻接表的头节点
AdjacencyNode[] headers;
/**
*********************
* 构造函数
*
* @param paraMatrix 图的邻接矩阵
*********************
*/
public AdjacencyList(int[][] paraMatrix) {
nodeNums = paraMatrix.length;
AdjacencyNode tempPreviousNode, tempNode;
headers = new AdjacencyNode[nodeNums];
for (int i = 0; i < nodeNums; i++) {
headers[i] = new AdjacencyNode(i);
tempPreviousNode = headers[i];
for (int j = 0; j < nodeNums; j++) {
if (paraMatrix[i][j] == 0) {
continue;
}//Of if
//i到j有边
tempNode = new AdjacencyNode(j);
//链接
tempPreviousNode.next = tempNode;
tempPreviousNode = tempNode;
}//Of for j
}//Of for i
}//Of AdjacencyList
/**
*********************
* toString方法
*********************
*/
public String toString() {
String resultString = "";
AdjacencyNode tempNode;
for (int i = 0;i < nodeNums; i++) {
tempNode = headers[i].next;
while (tempNode != null) {
resultString += "(" + i + "," + tempNode.column + ")";
tempNode = tempNode.next;
}//Of while
resultString += "\r\n";
}//Of for i
return resultString;
}//Of toString
/**
*********************
* 广度优先遍历
*
* @param paraStartIndex 出发顶点
*
* @return 访问序列
*********************
*/
public String breadthFirstTraversal(int paraStartIndex) {
CircleObjectQueue tempQueue = new CircleObjectQueue();
String resultString = "";
boolean tempVisitedArray[] = new boolean[nodeNums];
tempVisitedArray[paraStartIndex] = true;
resultString += paraStartIndex;
AdjacencyNode tempNode = headers[paraStartIndex];
tempQueue.enqueue(tempNode);
while (tempNode != null) {
tempNode = (AdjacencyNode) tempQueue.dequeue();
tempNode = tempNode.next;
while (tempNode != null) {
if(!tempVisitedArray[tempNode.column]) {
tempVisitedArray[tempNode.column] = true;
resultString += tempNode.column;
tempQueue.enqueue(tempNode);
}//Of if
tempNode = tempNode.next;
}//Of while
}//Of while
return resultString;
}//Of breadthFirstTraversal
/**
*********************
* 广度优先遍历测试
*********************
*/
public static void breadthFirstTraversalTest() {
//无向图测试
int[][] tempMatrix = { { 0, 1, 1, 0 }, { 1, 0, 0, 1 }, { 1, 0, 0, 1}, { 0, 1, 1, 0} };
Graph tempGraph = new Graph(tempMatrix);
System.out.println(tempGraph);
String tempSequence = "";
try {
tempSequence = tempGraph.breadthFirstTraversal(2);
} catch (Exception ee) {
System.out.println(ee);
}//Of try.
System.out.println("The breadth first order of visit: " + tempSequence);
}//Of breadthFirstTraversalTest
/**
*********************
* 程序入口
*
* @param args 暂未使用
*********************
*/
public static void main(String[] args) {
int[][] tempMatrix = { { 0, 1, 0 }, { 1, 0, 1 }, { 0, 1, 0 } };
AdjacencyList tempTable = new AdjacencyList(tempMatrix);
System.out.println("The data are:\r\n" + tempTable);
breadthFirstTraversalTest();
}//Of main
}//Of class AdjacencyList
运行结果
二、十字链表
十字链表是有向图的一种链式存储结构,在十字链表中图中每个顶点和每条弧都对应一个节点。
package JavDay8;
/**
* @author Kexiong Wang
*
* @date 2022年4月25日
*
* 十字链表
*/
public class OrthogonalList {
//十字链表节点
class OrthogonalNode {
//弧尾
int row;
//弧头
int column;
//同弧尾下一条弧
OrthogonalNode nextOut;
//同弧头下一条弧
OrthogonalNode nextIn;
/**
*********************
* 构造函数
*
* @param paraRow 弧尾
* @param paraColumn 弧头
*********************
*/
public OrthogonalNode(int paraRow, int paraColumn) {
row = paraRow;
column = paraColumn;
nextOut = null;
nextIn = null;
}//Of OrthogonalNode
}//Of class OrthogonalNode
//顶点数
int numNodes;
//顶点数组
OrthogonalNode[] headers;
/**
*********************
* 构造函数
*
* @param paraMatrix 图的邻接矩阵
*********************
*/
public OrthogonalList(int[][] paraMatrix) {
numNodes = paraMatrix.length;
//初始化
OrthogonalNode tempPreviousNode, tempNode;
headers = new OrthogonalNode[numNodes];
//链接同弧尾的节点
for (int i = 0; i < numNodes; i++) {
headers[i] = new OrthogonalNode(i, -1);
tempPreviousNode = headers[i];
for (int j = 0; j < numNodes; j++) {
if (paraMatrix[i][j] == 0) {
continue;
}//Of if
tempNode = new OrthogonalNode(i, j);
//链接
tempPreviousNode.nextOut = tempNode;
tempPreviousNode = tempNode;
}//Of for j
}//Of for i
//链接同弧头节点
OrthogonalNode[] tempColumnNodes = new OrthogonalNode[numNodes];
for (int i = 0; i < numNodes; i++) {
tempColumnNodes[i] = headers[i];
}//Of for i
for (int i = 0; i < numNodes; i++) {
tempNode = headers[i].nextOut;
while (tempNode != null) {
tempColumnNodes[tempNode.column].nextIn = tempNode;
tempColumnNodes[tempNode.column] = tempNode;
tempNode = tempNode.nextOut;
}//Of while
}//Of for i
}//Of the constructor
/**
*********************
* toString方法
*********************
*/
public String toString() {
String resultString = "Out arcs: \n";
OrthogonalNode tempNode;
for (int i = 0; i < numNodes; i++) {
tempNode = headers[i].nextOut;
while (tempNode != null) {
resultString += " (" + tempNode.row + ", " + tempNode.column + ")";
tempNode = tempNode.nextOut;
}//Of while
resultString += "\r\n";
}//Of for i
resultString += "\r\nIn arcs: \n";
for (int i = 0; i < numNodes; i++) {
tempNode = headers[i].nextIn;
while (tempNode != null) {
resultString += " (" + tempNode.row + ", " + tempNode.column + ")";
tempNode = tempNode.nextIn;
}//Of while
resultString += "\r\n";
}//Of for i
return resultString;
}//Of toString
/**
*********************
* 程序入口
*
* @param args 暂未使用
*********************
*/
public static void main(String args[]) {
int[][] tempMatrix = { { 0, 1, 0, 0 }, { 0, 0, 0, 1 }, { 1, 0, 0, 0 }, { 0, 1, 1, 0 } };
OrthogonalList tempList = new OrthogonalList(tempMatrix);
System.out.println("The data are:\r\n" + tempList);
}//Of main
}//Of class OrthogonalList
运行截图
三、图的应用
1. 最小生成树和Prim算法
若一棵树包含图G的所有顶点,且边的权值和最小,则称其为图G的最小生成树。
Prim算法构造最小生成树的过程是每次选取一个与当前顶点集合距离最近的节点(权值最小)加入T并构成一棵树,直到所有节点都在T中。
2. Dijkstra算法
Dijkstra算法通常用于求解单源最短路径问题。它的特点是通过循环求出发点到其他所有顶点的距离,每次循环确定出发点到一个顶点的最短距离并用于下一轮求解到其他顶点的最短距离。
3. 关键路径
在带权有向图中,具有最大路径长度的路径称为关键路径。
package JavDay8;
import JavaDay7.IntMatrix;
import java.util.Arrays;
/**
* @author Kexiong Wang
*
* @date 2022年4月25日
*/
public class Net {
//最大距离
public static final int MAX_DISTANCE = 10000;
//顶点数
int numNodes;
//路径长度矩阵(代价矩阵)
IntMatrix weightMatrix;
/**
*********************
* 构造函数
*
* @param paraNumNodes 图的顶点数
*********************
*/
public Net(int paraNumNodes) {
numNodes = paraNumNodes;
weightMatrix = new IntMatrix(numNodes, numNodes);
for (int i = 0; i < numNodes; i++) {
Arrays.fill(weightMatrix.getData()[i], MAX_DISTANCE);
}//Of for i
}//Of Net
/**
*********************
* 构造函数
*
* @param paraMatrix 图的权值矩阵
*********************
*/
public Net(int[][] paraMatrix) {
weightMatrix = new IntMatrix(paraMatrix);
numNodes = weightMatrix.getRows();
}//Of Net
/**
*********************
* toString方法
*********************
*/
public String toString() {
String resultString = "This is the weight matrix of the graph.\r\n" + weightMatrix;
return resultString;
}//Of toString
/**
*********************
* Dijkstra算法求单源最短路径
*
* @param paraSource 源顶点
* @return 到所有顶点的最短路径
*********************
*/
public int[] dijkstra(int paraSource) {
//初始化
int[] tempDistanceArray = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
tempDistanceArray[i] = weightMatrix.getValue(paraSource, i);
}//Of for i
//前顶点
int[] tempParentArray = new int[numNodes];
Arrays.fill(tempParentArray, paraSource);
tempParentArray[paraSource] = -1;
//记录是否被访问过
boolean[] tempVisitedArray = new boolean[numNodes];
tempVisitedArray[paraSource] = true;
int tempMinDistance;
int tempBestNode = -1;
for (int i = 0; i < numNodes - 1; i++) {
tempMinDistance = Integer.MAX_VALUE;
for (int j = 0; j < numNodes; j++) {
//被访问过
if (tempVisitedArray[j]) {
continue;
}//Of if
if (tempMinDistance > tempDistanceArray[j]) {
tempMinDistance = tempDistanceArray[j];
tempBestNode = j;
}//Of if
}//Of for j
tempVisitedArray[tempBestNode] = true;
//为第二轮确定顶点做准备
for (int j = 0; j < numNodes; j++) {
//被访问过
if (tempVisitedArray[j]) {
continue;
}//Of if
//不可达
if (weightMatrix.getValue(tempBestNode, j) >= MAX_DISTANCE) {
continue;
}//Of if
if (tempDistanceArray[j] > tempDistanceArray[tempBestNode] + weightMatrix.getValue(tempBestNode, j)) {
//更新最短距离
tempDistanceArray[j] = tempDistanceArray[tempBestNode] + weightMatrix.getValue(tempBestNode, j);
//更新前节点
tempParentArray[j] = tempBestNode;
}//Of if
}//Of for j
//System.out.println("The distance to each node: " + Arrays.toString(tempDistanceArray));
//System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
}//Of for i
//输出
System.out.println("Finally");
System.out.println("The distance to each node: " + Arrays.toString(tempDistanceArray));
System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
return tempDistanceArray;
}//Of dijkstra
/**
*********************
* Prim算法求最小生成树
*
* @return 最小的权值之和
*********************
*/
public int prim() {
//初始化
//任取一顶点为起点
int tempSource = 0;
int[] tempDistanceArray = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
tempDistanceArray[i] = weightMatrix.getValue(tempSource, i);
}//Of for i
int[] tempParentArray = new int[numNodes];
Arrays.fill(tempParentArray, tempSource);
//无前节点
tempParentArray[tempSource] = -1;
//被访问过的节点不再考虑
boolean[] tempVisitedArray = new boolean[numNodes];
tempVisitedArray[tempSource] = true;
int tempMinDistance;
int tempBestNode = -1;
for (int i = 0; i < numNodes - 1; i++) {
//寻找最佳节点
tempMinDistance = Integer.MAX_VALUE;
for (int j = 0; j < numNodes; j++) {
//被访问过
if (tempVisitedArray[j]) {
continue;
}//Of if
if (tempMinDistance > tempDistanceArray[j]) {
tempMinDistance = tempDistanceArray[j];
tempBestNode = j;
}//Of if
}//Of for j
tempVisitedArray[tempBestNode] = true;
//为下一次确定节点做准备
for (int j = 0; j < numNodes; j++) {
//被访问过
if (tempVisitedArray[j]) {
continue;
}//Of if
//顶点不可达
if (weightMatrix.getValue(tempBestNode, j) >= MAX_DISTANCE) {
continue;
}//Of if
if (tempDistanceArray[j] > weightMatrix.getValue(tempBestNode, j)) {
tempDistanceArray[j] = weightMatrix.getValue(tempBestNode, j);
tempParentArray[j] = tempBestNode;
}//Of if
}//Of for j
System.out.println(
"The selected distance for each node: " + Arrays.toString(tempDistanceArray));
System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
}//Of for i
int resultCost = 0;
for (int i = 0; i < numNodes; i++) {
resultCost += tempDistanceArray[i];
}//Of for i
System.out.println("Finally");
System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
System.out.println("The total cost: " + resultCost);
return resultCost;
}//Of prim
/**
*********************
* 寻找关键路径
*
* @return 关键路径上的节点
*********************
*/
public boolean[] criticalPath() {
// One more value to save simple computation.
int tempValue;
//求顶点的入度
int[] tempInDegrees = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
for (int j = 0; j < numNodes; j++) {
if (weightMatrix.getValue(i, j) != -1) {
tempInDegrees[j]++;
}//Of if
}//Of for j
}//Of for i
System.out.println("In-degree of nodes: " + Arrays.toString(tempInDegrees));
//拓扑排序
int[] tempEarliestTimeArray = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
//入度不为0不能移出
if (tempInDegrees[i] > 0) {
continue;
}//Of if
System.out.println("Removing " + i);
for (int j = 0; j < numNodes; j++) {
if (weightMatrix.getValue(i, j) != -1) {
tempValue = tempEarliestTimeArray[i] + weightMatrix.getValue(i, j);
if (tempEarliestTimeArray[j] < tempValue) {
tempEarliestTimeArray[j] = tempValue;
}//Of if
tempInDegrees[j]--;
}//Of if
}//Of for j
}//Of for i
System.out.println("Earliest start time: " + Arrays.toString(tempEarliestTimeArray));
//顶点的出度
int[] tempOutDegrees = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
for (int j = 0; j < numNodes; j++) {
if (weightMatrix.getValue(i, j) != -1) {
tempOutDegrees[i]++;
}//Of if
}//Of for j
}//Of for i
System.out.println("Out-degree of nodes: " + Arrays.toString(tempOutDegrees));
//逆拓扑排序
int[] tempLatestTimeArray = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
tempLatestTimeArray[i] = tempEarliestTimeArray[numNodes - 1];
}//Of for i
for (int i = numNodes - 1; i >= 0; i--) {
if (tempOutDegrees[i] > 0) {
continue;
}//Of if
System.out.println("Removing " + i);
for (int j = 0; j < numNodes; j++) {
if (weightMatrix.getValue(j, i) != -1) {
tempValue = tempLatestTimeArray[i] - weightMatrix.getValue(j, i);
if (tempLatestTimeArray[j] > tempValue) {
tempLatestTimeArray[j] = tempValue;
}//Of if
tempOutDegrees[j]--;
System.out.println("The out-degree of " + j + " decreases by 1.");
}//Of if
}//Of for j
}//Of for i
System.out.println("Latest start time: " + Arrays.toString(tempLatestTimeArray));
boolean[] resultCriticalArray = new boolean[numNodes];
for (int i = 0; i < numNodes; i++) {
if (tempEarliestTimeArray[i] == tempLatestTimeArray[i]) {
resultCriticalArray[i] = true;
}//Of if
}//Of for i
System.out.println("Critical array: " + Arrays.toString(resultCriticalArray));
System.out.print("Critical nodes: ");
for (int i = 0; i < numNodes; i++) {
if (resultCriticalArray[i]) {
System.out.print(" " + i);
}//Of if
}//Of for i
System.out.println();
return resultCriticalArray;
}//Of criticalPath
/**
*********************
* 程序入口
*
* @param args 暂未使用
*********************
*/
public static void main(String args[]) {
Net tempNet0 = new Net(3);
System.out.println(tempNet0);
int[][] tempMatrix1 = { { 0, 9, 3, 6 }, { 5, 0, 2, 4 }, { 3, 2, 0, 1 }, { 2, 8, 7, 0 } };
Net tempNet1 = new Net(tempMatrix1);
System.out.println(tempNet1);
//Dijkstra
tempNet1.dijkstra(1);
System.out.println("");
//无向图
int[][] tempMatrix2 = { { 0, 7, MAX_DISTANCE, 5, MAX_DISTANCE }, { 7, 0, 8, 9, 7 },
{ MAX_DISTANCE, 8, 0, MAX_DISTANCE, 5 }, { 5, 9, MAX_DISTANCE, 0, 15 },
{ MAX_DISTANCE, 7, 5, 15, 0 } };
Net tempNet2 = new Net(tempMatrix2);
tempNet2.prim();
//有向无环图
//-1表示无路径
int[][] tempMatrix3 = { { -1, 3, 2, -1, -1, -1 }, { -1, -1, -1, 2, 3, -1 },
{ -1, -1, -1, 4, -1, 3 }, { -1, -1, -1, -1, -1, 2 }, { -1, -1, -1, -1, -1, 1 },
{ -1, -1, -1, -1, -1, -1 } };
Net tempNet3 = new Net(tempMatrix3);
System.out.println("-------critical path");
tempNet3.criticalPath();
}//Of main
}//Of Net
运行结果