概念
图是由顶点和边组成的一种数据结构,我们之前介绍的树形结构中,树的每一个节点就是顶点,顶点与顶点中的连线就是边,也就是说,树是一种特殊的图
图中的边如果有方向,那么这个图就称为有向图,反之则成为无向图
完全图
如果一个无向图中任意两个节点之间有且仅有一条边,那么称这个图为无向完全图,而一个有向图中,任意两个节点间有且仅有方向相反的边,称之为有向完全图
下图中左侧为无向完全图,右侧为有向完全图
顶点的度
指的是有几条边与该节点关联,如果是完全图的话,那么分为入度和出度,箭头指向该节点的是这个节点的入度,反之为出度
下图中左侧的所有节点的度都是2,右侧所有节点的入度都是2,出度都是2,也即是所有节点的度都是4
路径
一个节点到另一个节点要走的边称之为路径,如果这些边上有权值(例如一个节点是北京,一个是天津,一个是上海,那么北京到天津的权值和北京到上海的权值是不一样的)
对于无向图,两个节点间的路径长度等于边的个数
对于有向图,两个节点间的路径长度等于所有边的权值之和
回路和环
对于一个路径,如果起点和终点都一样,那么称这个路径为回路,而如果这个路径的长度为一,也就是这个边是自己指向自己,称之为环
上图中黑色的三条边组成回路,红色的是环
子图
对于图G,如果图G1的节点都属于图G,边都属于图G,则称G1为G的子图
例如上图,右面的图就是左侧的图的子图
连通图
如果无向图中任意两个节点间都是有一条路径的,则称这个图是连通图
如果有向图中任意两个节点间都是有一条从起始点到终止点的路径,还有一条从终止点到起始点的路径,则称这个图是强连通图
一个连通图的最小连通子图是该图的生成树
图的存储
在计算机中,主要有下面两种方法来存储图
邻接矩阵
使用矩阵(二维数组来存储节点与节点之间的关系)
对于无向图,两个节点之间如果有边,就是1,或者是边的权值,没有就是0,自己和自己是0,因此,无向图的邻接矩阵表示是关于对角线对称的
对于有向图,一个节点如果有指向另一个节点的边,就是1或者是边的权值,否则就是正无穷,自己和自己是0
代码实现
定义一个最大值代表正无穷
public class Constant {
public static final int MAX = Integer.MAX_VALUE;
}
import java.util.Arrays;
/**
* 使用邻接矩阵存储图
*/
public class GraphByMatrix {
private char[] arrayV;//顶点数组
private int[][] matrix;//邻接矩阵
private boolean isDirect;//是否为有向图
/**
* @param size 当前矩阵的顶点个数
* @param isDirect
*/
public GraphByMatrix(int size, boolean isDirect){
this.arrayV = new char[size];
matrix = new int[size][size];
for (int i = 0; i < size; i++) {
Arrays.fill(matrix[i],Constant.MAX);
}
this.isDirect = isDirect;
}
/**
* 初始化顶点数组
* @param array
*/
public void initArrayV(char[] array){
for (int i = 0; i < arrayV.length; i++) {
arrayV[i] = array[i];
}
}
/**
* 添加边
* @param srcV 起点
* @param destV 终点
* @param weight 权值
*/
public void addEdge(char srcV, char destV, int weight){
int srcIndex = getIndexOfV(srcV);
int destIndex = getIndexOfV(destV);
matrix[srcIndex][destIndex] = weight;
//无向图的对称位置也有对应的权值
if(!isDirect){
matrix[destIndex][srcIndex] = weight;
}
}
/**
* 获取v顶点的下标
* @param v
* @return
*/
private int getIndexOfV(char v){
for (int i = 0; i < arrayV.length; i++) {
if (arrayV[i] == v){
return i;
}
}
return -1;
}
/**
* 获取顶点的度
* 有向图为入度+出度
* @param v
* @return
*/
public int getDevOfV(char v){
int count = 0;
int srcIndex = getIndexOfV(v);
//计算出度
for (int i = 0; i < arrayV.length; i++) {
if(matrix[srcIndex][i] != Constant.MAX){
count++;
}
}
//计算入度
if(isDirect){
for (int i = 0; i < arrayV.length; i++) {
if(matrix[i][srcIndex] != Constant.MAX){
count++;
}
}
}
return count;
}
private void printGraph() {
for (int i = 0; i < arrayV.length; i++) {
System.out.print(arrayV[i] + " ");
}
System.out.println();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if(matrix[i][j] == Constant.MAX){
System.out.print("∞ ");
} else {
System.out.print(matrix[i][j] + " ");
}
}
System.out.println();
}
}
public static void main(String[] args) {
GraphByMatrix graph = new GraphByMatrix(4,true);
char[] array = {'A','B','C','D'};
graph.initArrayV(array);
graph.addEdge('A','B',1);
graph.addEdge('A','D',1);
graph.addEdge('B','A',1);
graph.addEdge('B','C',1);
graph.addEdge('C','B',1);
graph.addEdge('C','D',1);
graph.addEdge('D','A',1);
graph.addEdge('D','C',1);
graph.printGraph();
System.out.println(graph.getDevOfV('A'));
}
}
邻接表
使用数组来表示顶点的集合,使用链表来表示边的关系
也就是说,每一个链表中存储了目标顶点所在的下标
而如果是有向图,那么会存储两个表,一个是入边表,另一个则是出边表
代码实现
import java.util.ArrayList;
/**
* 使用邻接表存储图
*/
public class GraphByNode {
static class Node{
public int src;//起始位置
public int dest;//目标位置
public int weight;//权重
public Node next;
public Node(int src, int dest, int weight) {
this.src = src;
this.dest = dest;
this.weight = weight;
}
}
public char[] arrayV;//存储顶点
public ArrayList<Node> edgList;//存储边
public boolean isDirect;
public GraphByNode(int size, boolean isDirect){
this.arrayV = new char[size];
edgList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
edgList.add(null);
}
this.isDirect = isDirect;
}
/**
* 初始化顶点数组
* @param array
*/
public void initArrayV(char[] array){
for (int i = 0; i < arrayV.length; i++) {
arrayV[i] = array[i];
}
}
/**
* 添加边
* @param srcV
* @param destV
* @param weight
*/
public void addEdge(char srcV, char destV, int weight){
int srcIndex = getIndexOfV(srcV);
int destIndex = getIndexOfV(destV);
addEdgeChild(srcIndex,destIndex,weight);
if(!isDirect){
addEdgeChild(destIndex,srcIndex,weight);
}
}
private void addEdgeChild(int srcIndex, int destIndex, int weight){
Node cur = edgList.get(srcIndex);
while(cur != null){
if(cur.dest == destIndex){
return;
}
cur = cur.next;
}
//之前没有存储过这条边
//头插法
Node node = new Node(srcIndex,destIndex,weight);
node.next = edgList.get(srcIndex);
edgList.set(srcIndex,node);
}
/**
* 获取v顶点的下标
* @param v
* @return
*/
private int getIndexOfV(char v){
for (int i = 0; i < arrayV.length; i++) {
if (arrayV[i] == v){
return i;
}
}
return -1;
}
/**
* 获取顶点的度
* @param v
* @return
*/
public int getDevOfV(char v){
int count = 0;
int srcIndex = getIndexOfV(v);
Node cur = edgList.get(srcIndex);
while(cur != null){
count++;
cur = cur.next;
}
if(isDirect){
int destIndex = srcIndex;
for (int i = 0; i < arrayV.length; i++) {
if(i == destIndex){
continue;
} else {
Node pCur = edgList.get(i);
while(pCur != null){
if(pCur.dest == destIndex){
count++;
}
pCur = pCur.next;
}
}
}
}
return count;
}
public void printGraph(){
for (int i = 0; i < arrayV.length; i++) {
System.out.println(arrayV[i] + " ->");
Node cur = edgList.get(i);
while(cur != null){
System.out.print(arrayV[cur.dest] + " ->");
cur = cur.next;
}
System.out.println();
}
}
public static void main(String[] args) {
GraphByNode graph = new GraphByNode(4,true);
char[] array = {'A','B','C','D'};
graph.initArrayV(array);
graph.addEdge('A','B',1);
graph.addEdge('A','D',1);
graph.addEdge('B','A',1);
graph.addEdge('B','C',1);
graph.addEdge('C','B',1);
graph.addEdge('C','D',1);
graph.addEdge('D','A',1);
graph.addEdge('D','C',1);
graph.printGraph();
System.out.println(graph.getDevOfV('A'));
}
}
全部代码
import java.util.*;
/**
* 使用邻接矩阵存储图
*/
public class GraphByMatrix {
private char[] arrayV;//顶点数组
private int[][] matrix;//邻接矩阵
private boolean isDirect;//是否为有向图
/**
* @param size 当前矩阵的顶点个数
* @param isDirect
*/
public GraphByMatrix(int size, boolean isDirect){
this.arrayV = new char[size];
matrix = new int[size][size];
for (int i = 0; i < size; i++) {
Arrays.fill(matrix[i],Constant.MAX);
}
this.isDirect = isDirect;
}
/**
* 初始化顶点数组
* @param array
*/
public void initArrayV(char[] array){
for (int i = 0; i < arrayV.length; i++) {
arrayV[i] = array[i];
}
}
/**
* 添加边
* @param srcV 起点
* @param destV 终点
* @param weight 权值
*/
public void addEdge(char srcV, char destV, int weight){
int srcIndex = getIndexOfV(srcV);
int destIndex = getIndexOfV(destV);
matrix[srcIndex][destIndex] = weight;
//无向图的对称位置也有对应的权值
if(!isDirect){
matrix[destIndex][srcIndex] = weight;
}
}
/**
* 获取v顶点的下标
* @param v
* @return
*/
private int getIndexOfV(char v){
for (int i = 0; i < arrayV.length; i++) {
if (arrayV[i] == v){
return i;
}
}
return -1;
}
/**
* 获取顶点的度
* 有向图为入度+出度
* @param v
* @return
*/
public int getDevOfV(char v){
int count = 0;
int srcIndex = getIndexOfV(v);
//计算出度
for (int i = 0; i < arrayV.length; i++) {
if(matrix[srcIndex][i] != Constant.MAX){
count++;
}
}
//计算入度
if(isDirect){
for (int i = 0; i < arrayV.length; i++) {
if(matrix[i][srcIndex] != Constant.MAX){
count++;
}
}
}
return count;
}
private void printGraph() {
for (int i = 0; i < arrayV.length; i++) {
System.out.print(arrayV[i] + " ");
}
System.out.println();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if(matrix[i][j] == Constant.MAX){
System.out.print("∞ ");
} else {
System.out.print(matrix[i][j] + " ");
}
}
System.out.println();
}
}
/**
* 广度优先遍历
* @param v
*/
public void bfs(char v){
boolean[] visited = new boolean[arrayV.length]; //close表
Queue<Integer> queue = new LinkedList<>();
int srcIndex = getIndexOfV(v);
queue.offer(srcIndex);
while(!queue.isEmpty()){
int top = queue.poll();
System.out.print(arrayV[top] + "->");
visited[top] = true;
for (int i = 0; i < arrayV.length; i++) {
if(matrix[top][i] != Constant.MAX && visited[i] == false){
queue.offer(i);
visited[i] = true;
}
}
}
}
/**
* 深度优先遍历
* @param v
*/
public void dfs(char v){
boolean[] visited = new boolean[arrayV.length]; //close表
int srcIndex = getIndexOfV(v);
dfsChild(srcIndex,visited);
}
private void dfsChild(int srcIndex, boolean visited[]){
System.out.print(arrayV[srcIndex] + "->");
visited[srcIndex] = true;
for (int i = 0; i < arrayV.length; i++) {
if(matrix[srcIndex][i] != Constant.MAX && visited[i] == false){
dfsChild(i,visited);
}
}
}
/**
* 边的抽象类
*/
static class Edge{
public int srcIndex;
public int destIndex;
public int weight;
public Edge(int srcIndex, int destIndex, int weight) {
this.srcIndex = srcIndex;
this.destIndex = destIndex;
this.weight = weight;
}
}
/**
* 求最小生成子树
* @param minTree 存储找到的边
* @return 最小生成树的权值和
*/
public int kruskal(GraphByMatrix minTree){
//存储所有的边
PriorityQueue<Edge> minQ = new PriorityQueue<>(new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
});
int n = arrayV.length;
UnionFindSet ufs = new UnionFindSet(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if(i < j && matrix[i][j] != Constant.MAX){
minQ.offer(new Edge(i, j, matrix[i][j]));
}
}
}
int size = 0;
int totalWight = 0;
while(size < n - 1 && !minQ.isEmpty()){
Edge edge = minQ.poll();
int srcIndex = edge.srcIndex;
int destIndex = edge.destIndex;
if(!ufs.isSameUnionFindSet(srcIndex,destIndex)){
minTree.addEdgeUseIndex(srcIndex,destIndex,edge.weight);
size++;
totalWight += edge.weight;
ufs.union(srcIndex, destIndex);
}
}
if(size == n - 1){
return totalWight;
} else {
return -1;
}
}
private void addEdgeUseIndex(int srcIndex, int destIndex, int weight){
matrix[srcIndex][destIndex] = weight;
//无向图的对称位置也有对应的权值
if(!isDirect){
matrix[destIndex][srcIndex] = weight;
}
}
/**
* 求最小生成子树
* @param minTree
* @param chv 起始点
* @return
*/
public int prim(GraphByMatrix minTree, char chv){
int srcIndex = getIndexOfV(chv);
//存储已经确定的点
Set<Integer> setX = new HashSet<>();
setX.add(srcIndex);
//存储尚未确定的点
Set<Integer> setY = new HashSet<>();
int n = arrayV.length;
for (int i = 0; i < n; i++) {
if(i != srcIndex){
setY.add(i);
}
}
//存储所有的边
PriorityQueue<Edge> minQ = new PriorityQueue<>(new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
});
//将所有与起始点有关的边放到优先级队列中
for (int i = 0; i < n; i++) {
if(matrix[srcIndex][i] != Constant.MAX){
minQ.offer(new Edge(srcIndex, i, matrix[srcIndex][i]));
}
}
int size = 0;
int totalWeight = 0;
while(!minQ.isEmpty()){
Edge min = minQ.poll();
int srcI = min.srcIndex;
int desT = min.destIndex;
if(setX.contains(desT)){
//构成环
} else {
minTree.addEdgeUseIndex(srcI, desT, min.weight);
size++;
totalWeight += min.weight;
if(size == n - 1){
return totalWeight;
}
//更新两个集合中的节点
setX.add(desT);
setY.remove(desT);
//将desT连接的所有边放到minQ
for (int i = 0; i < n; i++) {
if(matrix[desT][i] != Constant.MAX && !setX.contains(i)){
minQ.offer(new Edge(desT, i, matrix[desT][i]));
}
}
}
}
return -1;
}
public static void testGraphMinTreeKruskal() {
String str = "abcdefghi";
char[] array =str.toCharArray();
GraphByMatrix g = new GraphByMatrix(str.length(),false);
g.initArrayV(array);
g.addEdge('a', 'b', 4);
g.addEdge('a', 'h', 8);
//g.addEdge('a', 'h', 9);
g.addEdge('b', 'c', 8);
g.addEdge('b', 'h', 11);
g.addEdge('c', 'i', 2);
g.addEdge('c', 'f', 4);
g.addEdge('c', 'd', 7);
g.addEdge('d', 'f', 14);
g.addEdge('d', 'e', 9);
g.addEdge('e', 'f', 10);
g.addEdge('f', 'g', 2);
g.addEdge('g', 'h', 1);
g.addEdge('g', 'i', 6);
g.addEdge('h', 'i', 7);
GraphByMatrix kminTree = new GraphByMatrix(str.length(),false);
System.out.println(g.kruskal(kminTree));
kminTree.printGraph();
}
public static void testGraphMinTreePrim() {
String str = "abcdefghi";
char[] array = str.toCharArray();
GraphByMatrix g = new GraphByMatrix(str.length(), false);
g.initArrayV(array);
g.addEdge('a', 'b', 4);
g.addEdge('a', 'h', 8);
//g.addEdge('a', 'h', 9);
g.addEdge('b', 'c', 8);
g.addEdge('b', 'h', 11);
g.addEdge('c', 'i', 2);
g.addEdge('c', 'f', 4);
g.addEdge('c', 'd', 7);
g.addEdge('d', 'f', 14);
g.addEdge('d', 'e', 9);
g.addEdge('e', 'f', 10);
g.addEdge('f', 'g', 2);
g.addEdge('g', 'h', 1);
g.addEdge('g', 'i', 6);
g.addEdge('h', 'i', 7);
GraphByMatrix primTree = new GraphByMatrix(str.length(), false);
System.out.println(g.prim(primTree, 'a'));
primTree.printGraph();
}
/**
* 查找两节点最短路径
* @param vSrc 指定的节点
* @param dist 距离数组
* @param pPath 路径
*/
public void dijkstra(char vSrc, int[] dist, int[] pPath){
int srcIndex = getIndexOfV(vSrc);
//初始化距离数组
Arrays.fill(dist,Constant.MAX);
dist[srcIndex] = 0;
//初始化路径数组
Arrays.fill(pPath,-1);
pPath[srcIndex] = 0;
//创建数组,记录当前节点是否被记录
int n = arrayV.length;
boolean[] s = new boolean[n];
//n个顶点
for (int k = 0; k < n; k++) {
int min = Constant.MAX; //最小值
int u = srcIndex; //最小值下标
for (int i = 0; i < n; i++) {
if(s[i] == false && dist[i] < min){
min = dist[i];
u = i;
}
}
s[u] = true;
//松弛与u有关的所有顶点
for (int v = 0; v < n; v++) {
if(s[v] == false && matrix[u][v] != Constant.MAX && dist[u] + matrix[u][v] < dist[v]){
dist[v] = dist[u] + matrix[u][v];//更新距离
pPath[v] = u;//更新路径
}
}
}
}
public void printShortPath(char vSrc, int[] dist, int[] pPath){
int srcIndex = getIndexOfV(vSrc);
int n = arrayV.length;
for (int i = 0; i < n; i++) {
if(i != srcIndex){
ArrayList<Integer> path = new ArrayList<>();
int pathI = i;
while(pathI != srcIndex){
path.add(pathI);
pathI = pPath[pathI];
}
path.add(srcIndex);
Collections.reverse(path);
for (int pos: path){
System.out.print(arrayV[pos] + "-> ");
}
System.out.println(dist[i]);
}
}
}
public static void testGraphDijkstra() {
String str = "syztx";
char[] array = str.toCharArray();
GraphByMatrix g = new GraphByMatrix(str.length(),true);
g.initArrayV(array);
g.addEdge('s', 't', 10);
g.addEdge('s', 'y', 5);
g.addEdge('y', 't', 3);
g.addEdge('y', 'x', 9);
g.addEdge('y', 'z', 2);
g.addEdge('z', 's', 7);
g.addEdge('z', 'x', 6);
g.addEdge('t', 'y', 2);
g.addEdge('t', 'x', 1);
g.addEdge('x', 'z', 4);
int[] dist = new int[array.length];
int[] parentPath = new int[array.length];
g.dijkstra('s', dist, parentPath);
g.printShortPath('s', dist, parentPath);
}
public boolean bellmanFord(char vSrc, int[] dist, int[] pPath){
int srcIndex = getIndexOfV(vSrc);
//初始化距离数组
Arrays.fill(dist,Constant.MAX);
dist[srcIndex] = 0;
//初始化路径数组
Arrays.fill(pPath,-1);
pPath[srcIndex] = 0;
int n = arrayV.length;
//更新n次
for (int k = 0; k < n; k++) {
//遍历矩阵的每一个元素
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if(matrix[i][j] != Constant.MAX && dist[i] + matrix[i][j] < dist[j]){
dist[j] = dist[i] + matrix[i][j];
pPath[j] = i;
}
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if(matrix[i][j] != Constant.MAX && dist[i] + matrix[i][j] < dist[j]){
//存在负权回路
return false;
}
}
}
return true;
}
public static void testGraphBellmanFord() {
String str = "syztx";
char[] array = str.toCharArray();
GraphByMatrix g = new GraphByMatrix(str.length(),true);
g.initArrayV(array);
/* g.addEdge('s', 't', 6);
g.addEdge('s', 'y', 7);
g.addEdge('y', 'z', 9);
g.addEdge('y', 'x', -3);
g.addEdge('z', 's', 2);
g.addEdge('z', 'x', 7);
g.addEdge('t', 'x', 5);
g.addEdge('t', 'y', 8);
g.addEdge('t', 'z', -4);
g.addEdge('x', 't', -2);*/
//负权回路实例
g.addEdge('s', 't', 6);
g.addEdge('s', 'y', 7);
g.addEdge('y', 'z', 9);
g.addEdge('y', 'x', -3);
g.addEdge('y', 's', 1);
g.addEdge('z', 's', 2);
g.addEdge('z', 'x', 7);
g.addEdge('t', 'x', 5);
g.addEdge('t', 'y', -8);
g.addEdge('t', 'z', -4);
g.addEdge('x', 't', -2);
int[] dist = new int[array.length];
int[] parentPath = new int[array.length];
boolean flg = g.bellmanFord('s', dist, parentPath);
if(flg) {
g.printShortPath('s', dist, parentPath);
}else {
System.out.println("存在负权回路");
}
}
public void floydWarShall(int[][] dist, int[][] pPath){
int n = arrayV.length;
for (int i = 0; i < n; i++) {
Arrays.fill(dist[i],Constant.MAX);
Arrays.fill(pPath[i],-1);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if(matrix[i][j] != Constant.MAX){
dist[i][j] = matrix[i][j];
pPath[i][j] = i;
} else {
pPath[i][j] = -1;
}
if(i == j){
dist[i][j] = 0;
pPath[i][j] = -1;
}
}
}
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if(dist[i][k] != Constant.MAX && dist[k][j] != Constant.MAX
&& dist[i][k] + dist[k][j] < dist[i][j]){
dist[i][j] = dist[i][k] + dist[k][j];
//更新父节点路径
pPath[i][j] = pPath[k][j];
}
}
}
}
}
public static void main(String[] args) {
//testGraphMinTree();
//testGraphMinTreePrim();
//testGraphDijkstra();
testGraphBellmanFord();
}
}