一、什么图的广度优先遍历
简而言之,就是从指定的第一个节点开始,查找相连的所有节点,这是一层遍历,然后找第一个节点的每个相邻的节点的所有相邻节点,这就是第二层遍历,以此类推,知道遍历完所有的节点,很像二叉树的层序遍历,注意期间需要标记是否被访问过,且需要一个队列来装节点对应的索引。
二、核心思路
- 创建一个队列用于装nodelist的索引,cur为当前索引,next为邻接值索引 先装入指定的第一个节点到队列中,打印出这个索引对应的节点,并且标记为被访问过
- 在队列不为空时循环,弹出头部索引,以此获得第一个邻接值索引
- 判断这个邻接值是否存在,如果这个邻接值存在,判断这个邻接值是否被访问过,如果这个邻接值没有被访问过打印其对应节点,如果这个邻接值被访问过,再找第二个邻接值索引
- 这样不断的循环,找每个节点的相邻节点,一层一层的,直到找完
三、Java代码
package mypackage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
//队列类,用链表实现
class Queue<T> implements Iterable<T>{
// 节点个数,头节点,尾节点
private int N;
private Node head;
private Node last;
//节点类
public class Node {
public T data;
public Node next;
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
//构造方法,初始化
public Queue() {
this.N = 0;
this.head = new Node(null,null);
this.last = null;
}
//队列长度
public int size(){
return N;
}
//队列是否为空
public boolean isEmpty(){
return N==0;
}
//入队列
public void enqueue(T data){
// 如果队列为空,说明尾节点为空,让新节点为尾节点,头借点指向尾节点
if (isEmpty()){
last=new Node(data,null);
head.next=last;
// 如果队列不为空,让新节点为尾节点,老的尾节点指向新尾节点
}else {
Node oldlast=last;
last=new Node(data,null);
oldlast.next=last;
}
// 最后元素+1
N++;
}
//出队列,注意先入先出,每次出的节点就是head指向的第一个节点,然后让head只想第二个节点即可
// 且注意,如果队列为空,要将last=null
public T dequeue(){
// 如果为空,返回null
if (isEmpty()){
return null;
// 如果不为空,让head只想第二个节点,元素-1,且如果队列为空,要将last=null
}else {
Node oldfirst=head.next;
head.next=oldfirst.next;
N--;
if (isEmpty()){
last=null;
}
// 返回弹出的元素
return oldfirst.data;
}
}
// 遍历
@Override
public Iterator iterator() {
return new QIterator();
}
// 创建一个内部类实现Iterator接口
public class QIterator implements Iterator {
// 定义一个遍历的节点
private Node n;
public QIterator() {
// 初始化为0索引位置
this.n = head;
}
//重写两个方法
@Override
public boolean hasNext() {
// 这个方法判断是否超出最大索引,如果超出会停止遍历
return n.next != null;
}
@Override
public Object next() {
// 这个方法会遍历得每个节点
n = n.next;
return n.data;
}
}
}
class Graph {
// 成员变量,nodelist节点集合,如A,B,C----,matrix矩阵,numedge边的数量
public ArrayList<String> nodelist;
public int[][] matrix;
public int numedge;
// 定义一个是否访问过的数组
public boolean[] isVisited;
// 构造方法,传入节点个数
// 初始化边为0,初始化矩阵大小为nn,初始化集合大小为n
public Graph(int n) {
this.numedge = 0;
matrix = new int[n][n];
nodelist = new ArrayList<>(n);
// 这个数组放每个节点是否访问过的状态
isVisited = new boolean[n];
}
// 插入节点
public void insertnode(String node) {
nodelist.add(node);
}
// 添加边
// 先在矩阵中表示交叉处的值,再将边的数量+1
// v1,v2是索引,value是0或者1
public void insertedge(int v1, int v2, int value) {
matrix[v1][v2] = value;
// 因为是双向的,所以还要matrix[v2][v1]=value;
matrix[v2][v1] = value;
numedge++;
}
// 返回节点个数
public int getNumnode(){
return nodelist.size();
}
// 返回边的数量
public int getNumedge(){
return numedge;
}
// 返回节点i对应的数据
public String getnode(int i){
return nodelist.get(i);
}
// 返回v1,v2对应的value
public int getvalue(int v1,int v2){
return matrix[v1][v2];
}
// 显示图
// 循环打印一维数组即可
public void showgraph(){
for (int[] arr:matrix) {
System.out.println(Arrays.toString(arr));
}
}
// 广度优先遍历
// 找到某个节点i的第一个相邻节点,注意这里的i对应的是nodelist的索引,也表示矩阵的第几行
// 找到j表示矩阵的第几列,其实也就是nodelist的索引
public int firstAdjacentNode(int i) {
for (int j = 0; j < nodelist.size(); j++) {
// 如果等于1表示有连接
if (matrix[i][j] == 1) {
return j;
}
}
return -1;//循环完了都没找到邻接值返回-1
}
// 根据某个节点v1,v2的值,找到这个节点的相邻节点
// 为什么要设置这个方法呢,因为当找到某个节点的第一个邻接节点时,如果判断这个临界值被访问过
// 就还要再找下一个邻接节点,相当于根据第一个邻接节点的坐标找其邻接节点的索引j,找到j表示矩阵的第几列,其实也就是nodelist的索引
public int secondAdjacentNode(int v1,int v2) {
// 为什么要j = v2+1,就是从本行的下一列开始循环
for (int j = v2+1; j < getNumnode(); j++) {
if (matrix[v1][j] == 1) {
return j;
}
}
return -1;//循环完了都没找到邻接值
}
// 深度优先遍历核心方法
// 创建一个队列用于装nodelist的索引,cur为当前索引,next为邻接值索引
// 先装入首节点到队列中,打印出这个索引对应的节点,并且标记为被访问过
// 在队列不为空时循环,其中的程序包括,弹出头部索引,以此获得第一个邻接值索引
// 判断这个邻接值是否存在,如果存在判断是否被访问过,如果没有被访问过打印其节点,如果被访问过,再找第二个邻接值索引
// 这样不断的循环,找每个节点的相邻节点,一层一层的,直到找完
public void bfs(int i){
Queue<Integer> index=new Queue<>();
int cur;
int next;
index.enqueue(i);
System.out.print(getnode(i)+"--");
isVisited[i]=true;
while (!index.isEmpty()){
cur=index.dequeue();
next=firstAdjacentNode(cur);
//这个while循环找打所有的邻接节点
while (next!=-1){
if(!isVisited[next]){
System.out.print(getnode(next)+"--");
isVisited[next]=true;
index.enqueue(next);
}else {
next=secondAdjacentNode(cur,next);
}
}
}
}
public void bfs() {
// 对每一行都进行dfs,学习视频中说需要i的遍历,
// 其实这里我觉得不需要循环每一个,直接传入一个0索引,就会自动遍历完所有的,
//因为只要队列不为空,就会根据每次弹出一个队列中的元素,然后遍历完所有相连的元素,不存在单独的元素不被遍历到,不知道这个想法对不对?
for (int i = 0; i < getNumnode(); i++) {
// 如果没有被访问过
if (!isVisited[i]){
bfs(i);
}
}
}
}
/*
// 深度优先遍历
// 找到某个节点i的第一个相邻节点,注意这里的i对应的是nodelist的索引,也表示矩阵的第几行
// 找到j表示矩阵的第几列,其实也就是nodelist的索引
public int firstAdjacentNode(int i) {
for (int j = 0; j < nodelist.size(); j++) {
// 如果等于1表示有连接
if (matrix[i][j] == 1) {
return j;
}
}
return -1;//循环完了都没找到邻接值返回-1
}
// 根据某个节点v1,v2的值,找到这个节点的相邻节点
// 为什么要设置这个方法呢,因为当找到某个节点的第一个邻接节点时,如果判断这个临界值被访问过
// 就还要再找下一个邻接节点,相当于根据第一个邻接节点的坐标找其邻接节点的索引j,找到j表示矩阵的第几列,其实也就是nodelist的索引
public int secondAdjacentNode(int v1,int v2) {
// 为什么要j = v2+1,就是从本行的下一列开始循环
for (int j = v2+1; j < getNumnode(); j++) {
if (matrix[v1][j] == 1) {
return j;
}
}
return -1;//循环完了都没找到邻接值
}
// 开始核心算法,深度优先算法,deep first search
// 这里的i对应的是nodelist的索引,也表示矩阵的第几行
public void dfs(int i){
// 打印出这个节点,设置为被访问过
System.out.print(getnode(i)+"--");
isVisited[i]=true;
// 找下一个邻接节点
int next=firstAdjacentNode(i);
// 如果找到了,
// 判断是否被访问过,如果没有被访问过递归这个方法
// 如果被访问过,找这个邻接节点的邻接节点,循环while
while (next!=-1){
if (!isVisited[next]){
dfs(next);
}else {
next=secondAdjacentNode(i,next);
}
}
}
//对外调用方法,上面的dfs(int i)重载方法才是核心
public void dfs() {
// 对每一行都进行dfs
for (int i = 0; i < getNumnode(); i++) {
// 如果没有被访问过
if (!isVisited[i]){
dfs(i);
}
}
}
}
*/
//测试
public class MyJava {
public static void main(String[] args) {
// 初始化结点个数
Graph graph = new Graph(5);
// 添加节点
graph.insertnode("A");
graph.insertnode("B");
graph.insertnode("C");
graph.insertnode("D");
graph.insertnode("E");
// 添加边
// 连接关系为A-B,A-C,B-D,B-E,注意是双向的
// 其他的连接都是0
graph.insertedge(0, 1, 1);
graph.insertedge(0, 2, 1);
graph.insertedge(1, 2, 1);
graph.insertedge(1, 3, 1);
graph.insertedge(1, 4, 1);
// 显示图
System.out.println("图矩阵如下:");
graph.showgraph();
// 返回节点的个数
System.out.println("节点个数:" + graph.getNumnode());
// 返回边的个数
System.out.println("边个数:" + graph.getNumedge());
// 返回节点i对应的数据
System.out.println("节点集合中索引0处的节点:" + graph.getnode(0));
// 返回v1,v2对应的value
System.out.println("矩阵中,索引(1,0)处的值:" + graph.getvalue(1, 0));
// 优先深度遍历
// graph.dfs();
// 广度优先遍历
graph.bfs();
}
}