【数据结构与算法】Graph 图的深度及广度遍历
前言
什么是图
本文不赘述图的相关概念。简单的阐述一下个人对图的理解:图(Graph)是一中逻辑的数据关系,可以是使用数组(邻接矩阵)、数组加链表(邻接表)的形式保存和展示。
图分为有向和无向,也就是元素之间的邻接是否有方向;也分为有权与无权值,也就是元素之间的权值(可以理解为重庆到云南1000公里,这个1000就是权值)。
邻接矩阵就是一个二维数据来表示,其中arr[0][1] = 1 ,及表示0号元素和1号元素是相接的,这里0和1 ,一般情况是一个数组中元素的下标。值为1一般表示权值,如arr[重庆][云南] =1000,这个1000就是值两地的权值。一般情况下0代表自己如arr[1][1] = 0、不邻接一般用一个数字表示(自定),本问中,自己和不邻接在存储时都用0表示。
一、图的深度遍历
1、遍历其实就是,需要把这个图所包含的元素全部遍历出来。我在自己想的时候,可以想到使用递归的方式遍历。其实就符合深度遍历的概念
private ArrayList<String> vertexList;//存储顶点
private int[][] edges; //存储图对应的矩阵关系
private int munOfEdges; //边的个数
private boolean[] isVisited ; //用于记录哪一个节点是已经被访问了
public static void main(String[] args) {
int i = 5;
NoWayGraphArray noWayGraphArray = new NoWayGraphArray(i);
String[] vertexs = {"A","B","C","D","E"};
for (String vertex : vertexs) {
noWayGraphArray.insertVertex(vertex);
}
//这里只是在手动生产一张图
noWayGraphArray.insertEdge(0,1,1);
noWayGraphArray.insertEdge(1,2,1);
noWayGraphArray.insertEdge(0,2,1);
noWayGraphArray.insertEdge(1,3,1);
noWayGraphArray.insertEdge(1,4,1);
noWayGraphArray.showGraph();
noWayGraphArray.BFS();
}
public NoWayGraphArray(int n) {
edges = new int[n][n] ;
vertexList = new ArrayList<>(n);
munOfEdges = 0;
isVisited = new boolean[n];
}
public void insertVertex(String vertex){
vertexList.add(vertex);
}
/**
* 整个数据是用来表示,list元素中的关系的 使用一个矩阵来表示
* @param v1 表示当前元素在list中的下标 A->0
* @param v2 表示第二个元素在list中的下标 b->1
* @param weight 如果元素相连就是1 不相连接就是0 这个历史无向图,所以A<->b,所以赋值两次
*/
public void insertEdge(int v1,int v2,int weight){
edges[v1][v2] = weight;
edges[v2][v1] = weight;
munOfEdges++;
}
public int getNumOfVertex(){
return vertexList.size();
}
public int getNumOfEdges(){
return munOfEdges;
}
public String getValueByIndex(int i){
return vertexList.get(i);
}
public int getWeight(int v1,int v2){
return edges[v1][v2];
}
public void showGraph(){
for (int[] ints : edges){
System.out.println(Arrays.toString(ints));
}
}
最后的结果
例A B C D E
A [0, 1, 1, 0, 0]
B [1, 0, 1, 1, 1]
C [1, 1, 0, 0, 0]
D [0, 1, 0, 0, 0]
E [0, 1, 0, 0, 0]
完成了图的基础创建之后,开始深度遍历(depth first search )
1.思路
1、因为元素会被访问多次,需要一个标识来记录访问的情况boolean[]
2、深度遍历的逻辑,①首先选择一个顶点V,通过一个方法来获取到该顶点的最近一个邻接元素w②获取到w之后,将其作为新的顶点,获取w最近的邻接元素。这里已经开始递归了,这里要分如果有元素就判断,这个元素是不是已经访问过,没有重复②。如果没有或者已经访问过了,需要去找v中相对于w的下一个元素。(这里有点绕,我用数组在阐述一次,————(一开始我们进入递归,传入索引为0的第一个元素最为顶点,也就是A,找到A在矩阵中最近的一个邻接元素,就是B,arr[0][1] = 1 ,第一次访问B,弹出B,在用B的索引值1,继续递归,如果B的邻接元素全部遍完,或者没有邻接元素,这一个栈顶出栈之后,在最开始的A需要继续访问B之后的下一个邻接元素))
//图的DFS
public void DFS(){
for (int i =0 ;i<vertexList.size();i++){
if (!isVisited[i]){
System.out.print(vertexList.get(i)+"->");
this.DFS(i);
}
}
}
public void DFS(int i ){
this.isVisited[i] = true;
int w = this.getNextNeighbor(i);
while (w != -1){
if (!isVisited[w]){
System.out.print(vertexList.get(w)+"->");
this.DFS(w);
}
w = getNextNeighbor(i,w);
}
}
/**
* 这里就是从二维数组的矩阵中,确定纵轴的元素下标。arr[index][遍历该数组],找到第一个weight不为0的下标。
* @param index 从某个顶点开始,,找第一个临界点
* @return
*/
public int getNextNeighbor(int index){
for (int i = 0; i < vertexList.size(); i++) {
if (edges[index][i]>0){
return i;
}
}
return -1;
}
/**
* 这个方式是用于,当A->B时,当B作为本次递归的顶点时,需要排除A和已经扫描的节点,向后继续遍历
* @param index 需要获取到下一个节点的顶点
* @param v 需要排除的当前节点,逻辑需要找V,后面的一个链接的节点
* @return
*/
public int getNextNeighbor(int index ,int v){
for (int i = v + 1; i <vertexList.size(); i++) {
if (edges[index][i]>0)
return i;
}
return -1;
}
二、广度遍历
1.思路
1、整体思路是和深度相反的,这里创建一个LInkedList,用于存放在遍历某一个顶点时,获取到的所有的为访问的元素,再用循环遍历这个链表,不断弹出该链表的元素,进行遍历。最外层也需要一个循环来保证,左右的元素都遍历到
代码如下(示例):
public void BFS(){
for (int i = 0; i < vertexList.size(); i++) {
BFS(i);
}
}
public void BFS(int i){
LinkedList<Integer> queue = new LinkedList<>();
int n,w;
if (!isVisited[i]){
System.out.print(vertexList.get(i)+"->");
}
isVisited[i]= true;
queue.add(i);
while (!queue.isEmpty()){
n = queue.removeFirst();
w = getNextNeighbor(n);
while (w != -1){
if (!isVisited[w]){
System.out.print(vertexList.get(w)+"->");
queue.add(w);
isVisited[w] = true;
}
w = getNextNeighbor(n,w);
}
}
}
总结
以上就是图的深度和广度遍历的,个人想法和学习记录,欢迎大家指正!