二、图的遍历
1.深度优先DFS(使用递归)
2.广度优先BFS(使用队列)
3.DFS与BFS的比较
注:在邻接表与邻接矩阵的基础上实现DFS/BFS差距不大,这里都用邻接矩阵
核心代码:
//得到结点i的第一个邻接点
public int getFirstneighbor(int i){
for (int j=0;j<vertexNum;j++){
if (isVisited[j]==false && edgeMatrix[i][j]!=0){
return j;
}
}
return -1;
}
//得到结点i的下一个邻接点 j为i的上一个邻接点
public int getNextNeighbor(int i,int j){
for(int k=j+1; k<vertexNum; k++){
if (isVisited[k]==false && edgeMatrix[i][k]!=0){
return k;
}
}
return -1;
}
1.深度优先(使用递归)
深度优先遍历算法步骤
1.访问初始结点v,并标记结点v为已访问。
2.查找结点v的第一个邻接结点w。
3.若w存在,则继续执行4,如果w不存在,则回到第1步,将从v的下一个结点继续。
4.若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)。
5.查找结点v的w邻接结点的下一个邻接结点,转到步骤3。
public void dfs(){
isVisited = new boolean[vertexNum];
//无向图中 for循环的目的 是为了遍历非连通图 如果不是非连通图 一次循环就能遍历完毕
for (int i=0;i<vertexNum;i++){
if (isVisited[i]==false){
dfs(isVisited,i);
}
}
System.out.println();
}
public void dfs(boolean[] isVisited,int i ){
System.out.print(vertexList.get(i)+"=>");
isVisited[i] = true;
int j = getFirstneighbor(i);
while (j!=-1){
dfs(isVisited,j);
j=getNextNeighbor(i,j);
}
}
1.广度优先(使用队列)
广度优先算法步骤
1.访问初始结点v并标记结点v为已访问。
2.结点v入队列
3.当队列非空时,继续执行,否则算法结束。
4.出队列,取得队头结点u。
5.查找结点u的第一个邻接结点w。
6.若结点u的邻接结点w不存在,则转到步骤3;否则循环执行以下三个步骤:
6.1 若结点w尚未被访问,则访问结点w并标记为已访问。
6.2 结点w入队列
6.3 查找结点u的继w邻接结点后的下一个邻接结点w,转到步骤6。
核心代码如下
public void bfs(){
isVisited = new boolean[vertexNum];
for (int i=0;i<vertexNum;i++){
if(isVisited[i]==false){
bfs(isVisited,i);
}
}
System.out.println();
}
public void bfs(boolean[] isVisited,int i){
int u;//头结点下标
int w;//邻接结点下标
System.out.print(vertexList.get(i)+"->");
isVisited[i] = true;
LinkedList<Integer> queue = new LinkedList<>();
queue.addLast(i);
while (!queue.isEmpty()){
//取出队列头
u=queue.removeFirst();
w=getFirstneighbor(u);
while (w!=-1){
System.out.print(vertexList.get(w)+"->");
isVisited[w] = true;
queue.addLast(w);
w=getNextNeighbor(u,w);
}
}
}
对于如下图(加深理解作用)
DFS结果:A->B->C->G->D->F->H->P->E->I->J->K->N->O->L->M
BFS结果:A=>B=>C=>D=>E=>F=>G=>P=>I=>H=>J=>K=>L=>M=>N=>O
问:DFS中:当遍历到G点之后,下一个要遍历的是D F H中的哪一个?
答:遍历的是D,因为在创建图的时候是按照字母的顺序进行添加的。我们写的算法getFirstNeighbor()中,也是按照添加的顺序进行扫描的。(以上回答是基于邻接矩阵存储方式的,如果是邻接表,则情况不同(逆序))
在使用邻接表与邻接矩阵对同一个图进行DFS与BFS时,输出的顺序会不同:
在邻接矩阵中,对同一层的遍历是按照顶点数组的顺序进行的。
在邻接表中,为了减小时间复杂度,存储图时,我们都是使用的头插法添加结点,导致链表的结点顺序为数组中的逆序。这就会导致同层遍历时的顺序与邻接矩阵不一致。
下面贴完整代码(邻接矩阵方式):
package matrix;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
public class Graph2 {
ArrayList<Vertex> vertexList;
int[][] edgeMatrix; //初始化后都为0
int vertexNum; //顶点数量
int edgeNum; //边的数量
boolean[] isVisited;
public Graph2(int vertexNum) {
this.vertexNum = vertexNum;
vertexList = new ArrayList<>(vertexNum);
edgeMatrix = new int [vertexNum][vertexNum];
}
/**
* 插入顶点
* @param vertex
*/
public void insertVertex(Vertex vertex){
vertexList.add(vertex);
//this.vertexNum++;
}
/**\
* 添加边
* @param v1 第一个顶点的下标
* @param v2 第二个顶点的下标
* @param weight 边的权值
*/
public void insertEdge(int v1,int v2,int weight){
edgeMatrix[v1][v2] =weight;
edgeMatrix[v2][v1] =weight; //无向图就加上这一句
edgeNum++;
}
//图中常用的方法
//返回结点的个数
public int getNumOfVertex() {
return vertexNum;
}
//显示图对应的矩阵
public void showGraph() {
System.out.println(" " +vertexList);
int i=0;
for(int[] link : edgeMatrix) {
System.out.println(vertexList.get(i)+" "+Arrays.toString(link));
i++;
}
}
//得到边的数目
public int getNumOfEdges() {
return edgeNum;
}
//返回结点i(下标)对应的数据 0->"A" 1->"B" 2->"C"
public Vertex getValueByIndex(int i) {
return vertexList.get(i);
}
//返回v1和v2的权值
public int getWeight(int v1, int v2) {
return edgeMatrix[v1][v2];
}
//=============================DFS=================================
public void dfs(){
isVisited = new boolean[vertexNum];
//无向图中 for循环的目的 是为了遍历非连通图 如果没有孤立点 一次循环就能遍历完毕
for (int i=0;i<vertexNum;i++){
if (isVisited[i]==false){
dfs(isVisited,i);
}
}
System.out.println();
}
public void dfs(boolean[] isVisited,int i ){
System.out.print(vertexList.get(i)+"=>");
isVisited[i] = true;
int j = getFirstneighbor(i);
while (j!=-1){
dfs(isVisited,j);
j=getNextNeighbor(i,j);
}
}
public void bfs(){
isVisited = new boolean[vertexNum];
for (int i=0;i<vertexNum;i++){
if(isVisited[i]==false){
bfs(isVisited,i);
}
}
System.out.println();
}
public void bfs(boolean[] isVisited,int i){
int u;//头结点下标
int w;//邻接结点下标
System.out.print(vertexList.get(i)+"->");
isVisited[i] = true;
LinkedList<Integer> queue = new LinkedList<>();
queue.addLast(i);
while (!queue.isEmpty()){
//取出队列头
u=queue.removeFirst();
w=getFirstneighbor(u);
while (w!=-1){
System.out.print(vertexList.get(w)+"->");
isVisited[w] = true;
queue.addLast(w);
w=getNextNeighbor(u,w);
}
}
}
public int getFirstneighbor(int i){
for (int j=0;j<vertexNum;j++){
if (isVisited[j]==false && edgeMatrix[i][j]!=0){
return j;
}
}
return -1;
}
public int getNextNeighbor(int i,int j){
for(int k=j+1; k<vertexNum; k++){
if (isVisited[k]==false && edgeMatrix[i][k]!=0){
return k;
}
}
return -1;
}
public static void main(String[] args) {
Graph2 g = new Graph2(8);
g.insertVertex(new Vertex("A"));
g.insertVertex(new Vertex("B"));
g.insertVertex(new Vertex("C"));
g.insertVertex(new Vertex("D"));
g.insertVertex(new Vertex("E"));
g.insertVertex(new Vertex("F"));
g.insertVertex(new Vertex("G"));
g.insertVertex(new Vertex("H"));
g.insertEdge(0, 1, 1);//A-B
g.insertEdge(0, 2, 1);//A-C
g.insertEdge(0, 3, 1);//A-D
g.insertEdge(0, 4, 1);//A-E
g.insertEdge(1, 2, 1);//B-C
g.insertEdge(1, 5, 1);//B-F
g.insertEdge(2, 6, 1);//C-G
g.insertEdge(3, 6, 1);//D-G
g.insertEdge(5, 6, 1);//F-G
g.insertEdge(7, 6, 1);//H-G
g.showGraph();
System.out.println("DFS:");
g.dfs();
System.out.println("BFS:");
g.bfs();
}
}
/**
* 顶点类
*/
class Vertex {
String vertexName;
public Vertex(String vertexName) {
this.vertexName = vertexName;
}
@Override
public String toString() {
return vertexName ;
}
}
邻接表:
package edge;
import java.util.Scanner;
/**
* 数组加链表形式 数组是vertexList ,用ArrayList来代替
* 邻接链表使用顶点所在索引(当作边的对象)来构建图
*
* @author King
*/
public class CreateGraph {
/**
* 根据顶点信息String,返回边的对象
*
* @param graph 图
* @param str 顶点名称
* @return 返回的是边对象的标签
*/
public int vtoe(Graph graph, String str) {
for (int i = 0; i < graph.vertexNum; i++) {
if (graph.vertexList.get(i).vertexName.equals(str)) {
return i;
}
}
return -1;
}
/**
* 该函数用于图的初始化的实现
*
* @param graph 图
*/
public void initialGraph(Graph graph) {
@SuppressWarnings("resource")
Scanner scan = new Scanner(System.in);
System.out.println("请输入顶点数和边数:");
graph.vertexNum = scan.nextInt();
graph.edgeNum = scan.nextInt();
System.out.println("请依次输入定点名称:");
for (int i = 0; i < graph.vertexNum; i++) {
Vertex vertex = new Vertex();
String name = scan.next();
vertex.vertexName = name;
vertex.firstEdge = null;
graph.vertexList.add(vertex);
}
System.out.println("请依次输入每个边:");
for (int i = 0; i < graph.edgeNum; i++) {
String preV = scan.next();
String folV = scan.next();
int v1 = vtoe(graph, preV);
int v2 = vtoe(graph, folV);
if (v1 == -1 || v2 == -1)
System.out.println("输入顶点数据错误!");
//下面代码是图构建的核心:链表操作
Edge edge1 = new Edge();
edge1.edgeName = v2; //当第一次输入v1 v2时 v2=1 因为List数组从0开始
//使用头插法插入顶点V1的链表中
edge1.next = graph.vertexList.get(v1).firstEdge;
graph.vertexList.get(v1).firstEdge = edge1;
// 下面代码加上便是构建无向图,不加便是构建有向图
Edge edge2=new Edge();
edge2.edgeName=v1;
edge2.next=graph.vertexList.get(v2).firstEdge;
graph.vertexList.get(v2).firstEdge=edge2;
}
}
/**
* 输出图的邻接链表表示
*
* @param graph 图
*/
public void outputGraph(Graph graph) {
Edge edge = new Edge();
for (int i = 0; i < graph.vertexNum; i++) {
System.out.print(graph.vertexList.get(i).vertexName);
edge = graph.vertexList.get(i).firstEdge;
while (edge != null) {
System.out.print("-->" + graph.vertexList.get(edge.edgeName).vertexName);
edge = edge.next;
}
System.out.println();
}
}
public static void main(String[] args) {
CreateGraph createGraph = new CreateGraph();
Graph graph = new Graph();
createGraph.initialGraph(graph);
createGraph.outputGraph(graph);
graph.dfs();
graph.bfs();
}
}
-------------------------------------------------
package edge;
/**
* 图的边对象类
* @author King
*/
public class Edge {
int edgeName=-1;
Edge next;
public Edge(){
}
}
-------------------------------------------------
package edge;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* 数组加链表形式 数组是vertexList ,用ArrayList来代替
*/
public class Graph {
ArrayList<Vertex> vertexList=new ArrayList<Vertex>();
int vertexNum=0;
int edgeNum=0;
boolean[] isVisited;
public Graph(){}
public void dfs(){
isVisited = new boolean[vertexNum];
System.out.println("DFS:");
for(int i=0;i<vertexNum;i++){
if(isVisited[i]==false){
dfs(isVisited,i);
}
}
System.out.println();
}
private void dfs(boolean[] isVisited, int i) {
System.out.print(vertexList.get(i)+"->");
isVisited[i] = true;
Edge edge =vertexList.get(i).firstEdge;
while(edge!=null&&edge.edgeName!=-1 ){
if (isVisited[edge.edgeName]==false){
dfs(isVisited,edge.edgeName);
}
edge=edge.next;
}
}
public void bfs(){
isVisited = new boolean[vertexNum];
System.out.println("BFS:");
for(int i=0;i<vertexNum;i++){
if(isVisited[i]==false){
bfs(isVisited,i);
}
}
System.out.println();
}
private void bfs(boolean[] isVisited, int i) {
LinkedList<Integer> queue = new LinkedList<>();//队列
System.out.print(vertexList.get(i)+"->");
isVisited[i] = true;
queue.addLast(i);
while (!queue.isEmpty()){
int w =queue.removeFirst();
Edge edge =vertexList.get(w).firstEdge;
while (edge!=null){
if (isVisited[edge.edgeName]==false ){
System.out.print(vertexList.get(edge.edgeName));
isVisited[edge.edgeName]=true;
queue.addLast(edge.edgeName);
}
edge=edge.next;
}
}
}
}
----------------------------------------------
package edge;
/**
*
*
* Vertex对象类<这里顶点对象只是辅助边构建图,不是作为邻接链表的对象>
*
*
* 图的点对象类
* @author King
*/
public class Vertex {
String vertexName;
Edge firstEdge=new Edge();
public Vertex(){
}
@Override
public String toString() {
return vertexName ;
}
}