目录
一、图的知识点:
1. 图的概念:图是由顶点集合及顶点间的关系集合组成的一种数据结构。
2. 图的定义:
G = (V, E) // V代表顶点的集合, E代表边的集合
V = {x | x ∈ 某个数据元素集合}
E = {(x, y) | x, y ∈ V} // 无向图
或者
E = {<x, y> | x, y ∈ V 并且 Path(x, y)} // 有向图 Path代表单向通路
3. 顶点和边:图中的结点成为顶点;两个顶点相关联,则称两个顶点之间有一条边。
4. 有向图:边(也可称为弧)带有方向,在有向图中顶点对<x,y>是有序的。顶点对<x,y>称为从顶点x到顶点y的一条有向边。因此<x,y>和<y,x>并不是同一条边。
5. 无向图:边不带有方向,在无向图中顶点对(x,y)称为与顶点x和顶点y相关联的一条边。因此(x,y)和(y,x)是同一条边。
6. 完全图:在有n个顶点的无向图中,有n*(n-1)/2条边,即任意两个顶点之间有且只有一条边。
7. 邻接顶点:若两个顶点有关联,则两个顶点邻接;
在无向图中两个顶点互为邻接点;在有向图中,<x,y>是其中的一条边,则称顶点x邻接到顶点y,顶点y邻接自顶点x。
8. 顶点的度:与顶点相关联边的的条数,如上图无向图中D的度为3,有向图中D的度 = 入度(2)+ 出度(1)= 3;
有向图中,顶点的度 = 入度(进入的边) + 出度(出去的边)。
9. 路径:在图中,从顶点v1出发到顶点v2所经过的顶点序列就称为顶点v1到顶点v2的路径,如上无向图 顶点A 到 顶点E 的其中一条路径:A->D->E。
10. 路径长度:对于不带权的图,一条路径的路径长度 = 该路径的边的条数总和;
对于带权的图,一条路径的路径长度 = 该路径上的各个边的权值的总和。
二、图的实现(都包含在Graph类中)
1. 图的基本实现:
import java.util.*;
public class Graph {
private ArrayList<String> vertexList; //
private int[][] arcs; // 存储图对应的邻接矩阵
private int arcnum; // 边的数目
private int GraphKind; //0有向图 1无向图
private boolean[] visited;// 用于判断顶点是否被遍历
//构造器
public Graph(int n, int kind) {
//初始化矩阵和顶点
arcs = new int[n][n];
vertexList = new ArrayList<>(n);
arcnum = 0;
GraphKind = kind;
visited = new boolean[n];
}
//插入结点
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
//添加边
//v1代表第一个顶点对应的下标
//v2代表第二个顶点对应的下标
public void insertArcs(int v1, int v2, int weight) {
arcs[v1][v2] = weight;
if(GraphKind != 0) {
arcs[v2][v1] = weight;
}
arcnum++;
}
//图中常用的方法:
//1. 返回结点的个数
public int getNumOfVertex() {
return vertexList.size();
}
//2. 得到边的数目
public int getNumOfArcs() {
return arcnum;
}
//3. 返回顶点对应的下标
public String getValueByIndex(int index) {
return vertexList.get(index);
}
//4. 返回v1和v2的权值
public int getWeight(int v1, int v2) {
return arcs[v1][v2];
}
//5. 显示图对应的矩阵
public void showGraph() {
System.out.print(" " + "\t");
for (String s : vertexList) {
System.out.print(s + "\t");
}
System.out.println();
for (int i = 0; i < vertexList.size(); i ++) {
System.out.print(vertexList.get(i) + "\t");
for (int j = 0; j < vertexList.size(); j ++) {
System.out.print(arcs[i][j] + "\t");
}
System.out.println();
}
}
}
2. 图的邻接点
//得到第一个邻接节点
public int getFirst(int index) {
for (int i = 0; i < vertexList.size(); i ++) {
if (arcs[index][i] > 0) {
return i;
}
}
return -1;
}
//得到下一个邻接节点
public int getNext(int v1, int v2) {
for (int i = v2 + 1; i < vertexList.size(); i ++) {
if (arcs[v1][i] > 0) {
return i;
}
}
return -1;
}
1)邻接节点的判断:
对于上图无向图所得到的邻接矩阵如下:
判断D的第一个邻接顶点,则观察D的那一行,然后看在D的那一行最前面的第一个与D有关联的顶点,则可知,D的第一个邻接顶点是 顶点A,A的第一个邻接节点为 B;
判断D的下一个邻接顶点,则观察D的那一行,与D有关联的顶点都能算作D的下一个邻接节点;
三、图的遍历(包含在类Graph中)
(一) 深度优先遍历
1. 原理:
以从顶点A开始遍历为例:
1)A 是第一个遍历的顶点,标记A,寻找A的第一个邻接顶点可知没有,故寻找A的下一个邻接顶点,A与B、D都有边,但根据顶点顺序可知下一个邻接顶点为 B,所以标记B;
2)以B为新的遍历的顶点,寻找B的第一个邻接顶点可知为A,但A已经被遍历,所以寻找B的下一个邻接顶点为E,所以标记E;
3)以E为新的遍历的顶点,寻找E的第一个邻接顶点可知为C,且C并未被遍历,所以标记C;
4)以C为新的遍历的顶点,寻找C的第一个邻接顶点可知没有,所以寻找C的下一个邻接顶点为D,所以标记D;
5)以D为新的遍历的顶点,寻找D的第一个邻接顶点为A,但A已经被遍历,所以寻找D的下一个邻接顶点为E,但E已经被遍历,故整个遍历结束;
遍历结果为: A B E C D
2. 代码实现:
//深度优先遍历
//对连通图
public void dfs (int v) {
System.out.print(getValueByIndex(v) + "\t");
//将该节点设置为已访问
visited[v] = true;
int w;
w = getFirst(v);
//查找结点i的第一个邻接节点
while (w != -1) {
if (!visited[w]) {
dfs(w);
}
w = getNext(v, w);
}
}
//对深度优先遍历进行重载,遍历所有结点并进行深度优先遍历
//即对非连通图的深度优先遍历
public void dfs() {
//对visited 进行初始化
for (int i = 0; i < vertexList.size(); i ++) {
visited[i] = false;
}
for (int i = 0; i < vertexList.size(); i ++) {
if (!visited[i]) {
dfs(i);
}
}
}
对上图无向图从顶点A进行深度优先遍历,从顶点C进行深度优先遍历:
(二) 广度优先遍历
1. 原理:(借助队列)
以从顶点A开始遍历为例:
1)A 是第一个遍历的顶点,标记A并且使A进入队列,寻找A的第一个邻接顶点可知没有,故寻找A的下一个邻接顶点,A与B、D都有边,但根据顶点顺序可知下一个邻接顶点为 B,所以标记B并且使B进入队列;
2)此时A作为队列的头,将A出队列,继续以顶点A进行遍历,寻找B以后的邻接顶点可知为D,故标记D,使D进入队列,A所有有关联的顶点皆已经遍历完;
3)此时B作为队列的头,将B出队列,以顶点B进行遍历,寻找B的第一个邻接顶点可知为A,但A已经被遍历,因此寻找下一个邻接顶点为E,所以标记E,使E进入队列,B所有有关联的顶点皆已经遍历完;
4)此时D作为队列的头,将D出队列,以顶点D进行遍历,寻找D的第一个邻接顶点可知为A,所以寻找下一个邻接顶点为E,但E已经被标记;
5)此时E作为队列的头,将E出队列,以顶点E进行遍历,寻找E的第一个邻接顶点为C,C未被遍历因此标记C,故整个遍历结束;
故遍历结果为:A B D E C
2. 代码实现:
//广度优先遍历
//连通图的广度优先遍历
public void bfs(int v) {
int u, w; // u代表队列的头结点对应的下标。w代表邻接节点
Queue<Integer> q = new ArrayDeque<>();
System.out.print(getValueByIndex(v) + "\t");
//标记已访问的结点
visited[v] = true;
//将已访问的结点加入队列
q.add(v);
while (!q.isEmpty()) {
//取出队列的头
u = q.poll();
//得到第一个邻接点的下标
w = getFirst(u);
while (w != -1) {
if (!visited[w]) {
//如果第一个邻接点未被访问则访问第一个邻接节点
System.out.print(getValueByIndex(w) + "\t");
visited[w] = true;
q.add(w);
}
//第一个邻接节点已经被访问,找u的下一个邻接节点
w = getNext(u, w);
}
}
}
//遍历所有的结点 进行广度优先遍历
//对非连通图的广度优先遍历
public void bfs() {
for (int i = 0; i < vertexList.size(); i ++) {
visited[i] = false;
}
for (int i = 0; i <vertexList.size(); i ++) {
if (!visited[i]) {
bfs(i);
}
}
}
对上图无向图从顶点A进行广度优先遍历,从顶点C进行广度优先遍历: