算法思路
- 节点有多个后继节点时先遍历边权重小的
- 指定一个起始节点
- 把当前节点所有后继节点按路径权值升序排序并加入到集合中
- 按顺序弹出后继节点,将此节点的所有未被遍历的后继节点按权值升序排序加入集合
- 重复弹出集合首元素以及把该元素后继节点加入集合直到集合为空
算法实现
图的实现
- 此实现方法没有节点类
- 用枚举类型
UNDISCOVERD
表示未被发现,VISITED
表示已被访问 - 采用邻接矩阵和顶点索引
- 邻接矩阵
int[][] matrix
(邻接矩阵无需设置为沿对角线对称)matrix[i][j]
表示从索引i
的节点指向索引j
的节点的权值- 权值为0表示两点不连接或者自身与自身不连接
List<Integer> toVisit
保存整个遍历过程待访问的节点
public class Graph<T> {
private int N; // 节点个数
public int[][] matrix; // 邻接矩阵
private Status[] statuses; // 保存每个节点的状态
private T[] datas; // 保存每个节点的数据
private List<Integer> toVisit = new ArrayList<>(); // 保存整个遍历过程待访问的节点
}
enum Status { // 节点对象的状态
// 未被发现, 已被遍历
UNDISCOVERD, VISITED
}
重点
- 将当前节点的后继节点按权值排序并加入集合
- 遍历每个节点,查看邻接矩阵
如果matrix[index][i] > 0
则说明当前节点有边指向索引i
的节点,然后再判断该节点是否被访问过,跳过被访问过的节点 - 按权值排序需要重写
compare()
方法(默认升序)
o1 和o2 是两个对象也即两节点索引,return matrix[index][o1] - matrix[index][o2]
的作用是,集合调用sort()方法进行排序时,按前当前节点指向索引o1
节点的边权重减去当前节点指向索引o2
节点的边权重,小于0(前一条边的权重小)则两条边的位置不变,大于0则交换位置(大概意思是这样)
- 遍历每个节点,查看邻接矩阵
- 将当前节点后继节点的集合合并
addAll()
到整个遍历过程的待访问集合
this.toVisit
是图对象的成员变量,记录整个遍历过程的待访问节点
toVisit
是当前方法的局部变量记录当前节点的后继待访问节点- 如果不用局部变量
toVisit
而只用this.toVisit
则会出现排序时打乱了上一个节点的后继节点的顺序,影响遍历
- 如果不用局部变量
// 将当前节点的所有后继节点存入队列
for (int i = 0; i < N; i++) {
if (matrix[index][i] > 0 && statuses[i] == Status.UNDISCOVERD) {
// 设置该节点状态为已被遍历
statuses[i] = Status.VISITED;
toVisit.add(i);
}
}
toVisit.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return matrix[index][o1] - matrix[index][o2];
}
});
this.toVisit.addAll(toVisit);
算法主体方法
- 将第一个节点索引加入
this.toVisit
并设置为已访问 - 只要集合
this.toVisit
不为空,则循环执行- 局部变量
List<Integer> toVisit
保存当前节点的后继节点 - 弹出集合第一个元素并输出节点储存的数据
datas[index]
- 遍历每个节点找出当前节点的后继节点
- 将当前节点的所有后继节点存入集合
toVisit
并设置为已访问VISITED
- 对
toVisit
按权值排序 - 合并集合
this.toVisit.addAll(toVisit)
- 局部变量
/**
* 广度优先遍历
*
* @return void
*/
public void breadthFirstTravel() {
this.toVisit.add(0);
statuses[0] = Status.VISITED;
while (!this.toVisit.isEmpty()) { // 只要待访问的队列不为空
// 弹出本节点
int index = this.toVisit.remove(0);
System.out.println(datas[index]);
// 保存当前节点的后继节点(新建一个toVisit是为了防止排序时影响上一轮的排序)
List<Integer> toVisit = new ArrayList<>();
// 将当前节点的所有后继节点存入队列
for (int i = 0; i < N; i++) {
if (matrix[index][i] > 0 && statuses[i] == Status.UNDISCOVERD) {
// 设置该节点状态为已被遍历
statuses[i] = Status.VISITED;
toVisit.add(i);
}
}
toVisit.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return matrix[index][o1] - matrix[index][o2];
}
});
this.toVisit.addAll(toVisit);
}
}
测试
- 6个节点,对应保存数据为字母ABCDEF
int[][] set
是为了初始化邻接矩阵graph.setMatrix(set[i][0], set[i][1], set[i][2])
- 执行广度优先遍历
public static void main(String[] args) {
Graph<String> graph = new Graph<>(6);
graph.setDatas(new String[]{"A", "B", "C", "D", "E", "F"});
int[][] set = {{0, 1, 1},
{0, 2, 3},
{1, 3, 2},
{1, 5, 2},
{2, 3, 4},
{2, 5, 7},
{3, 4, 1},
{4, 5, 8}};
for (int i = 0; i < set.length; i++) {
graph.setMatrix(set[i][0], set[i][1], set[i][2]);
}
graph.breadthFirstTravel();
}
输出结果:A B C D F E
完整代码
public class Graph<T> {
private int N; // N个节点
public int[][] matrix; // 邻接矩阵
private Status[] statuses; // 保存每个节点的状态
private T[] datas; // 保存每个节点的数据
private List<Integer> toVisit = new ArrayList<>(); // 保存整个遍历过程待访问的节点
public Graph(int N) {
this.N = N;
matrix = new int[N][N];
statuses = new Status[N];
datas = (T[]) new Object[N]; // 泛型数组实例化
initStatuses();
}
public void setDatas(T[] datas) {
this.datas = datas;
}
/**
* 初始化状态数组
*
* @return void
*/
private void initStatuses() {
for (int i = 0; i < N; i++) {
statuses[i] = Status.UNDISCOVERD;
}
}
/**
* 邻接矩阵保存的信息是从一个节点指向另一个节点的信息
*
* @param from 从这个节点
* @param to 指向这个节点
* @param weight 路径权重
* @return void
*/
public void setMatrix(int from, int to, int weight) {
matrix[from][to] = weight;
}
/**
* 广度优先遍历
*
* @return void
*/
public void breadthFirstTravel() {
this.toVisit.add(0);
statuses[0] = Status.VISITED;
while (!this.toVisit.isEmpty()) { // 只要待访问的队列不为空
// 弹出本节点
int index = this.toVisit.remove(0);
System.out.println(datas[index]);
// 保存当前节点的后继节点(新建一个toVisit是为了防止排序时影响上一轮的排序)
List<Integer> toVisit = new ArrayList<>();
// 将当前节点的所有后继节点存入队列
for (int i = 0; i < N; i++) {
if (matrix[index][i] > 0 && statuses[i] == Status.UNDISCOVERD) {
// 设置该节点状态为已被遍历
statuses[i] = Status.VISITED;
toVisit.add(i);
}
}
toVisit.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return matrix[index][o1] - matrix[index][o2];
}
});
this.toVisit.addAll(toVisit);
}
}
public static void main(String[] args) {
Graph<String> graph = new Graph<>(6);
graph.setDatas(new String[]{"A", "B", "C", "D", "E", "F"});
int[][] set = {{0, 1, 1},
{0, 2, 3},
{1, 3, 2},
{1, 5, 2},
{2, 3, 4},
{2, 5, 7},
{3, 4, 1},
{4, 5, 8}};
for (int i = 0; i < set.length; i++) {
graph.setMatrix(set[i][0], set[i][1], set[i][2]);
}
graph.breadthFirstTravel();
}
}
enum Status { // 节点对象的状态
// 未被发现, 已被遍历
UNDISCOVERD, VISITED
}