图是一种数据结构,由一些顶点(节点)连接而成。
可以用list来保存节点,numOfEdges保存边的条数,用一个同等大小的Boolean数组表示节点是否被遍历过。用邻接矩阵来表示这个图的关系。
邻接矩阵:
如果两个节点相连则设为1,否则为0,这样便可以构造出一个矩阵。
public class Graph {
private ArrayList<String> vertexList;//节点
private int[][] edges;//矩阵
private int numOfEdges;//边数
private boolean[] isVisited;//记录是否被访问过
public Graph(int n){
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
numOfEdges = 0;
}
//获取结点个数
public int getNumOfVertex(){
return vertexList.size();
}
//获取边的个数
public int getNumOfEdges(){
return numOfEdges;
}
//获取结点i对应的数据
public String getValByIndex(int i){
return vertexList.get(i);
}
//返回权值
public int getWeight(int v1, int v2){
return edges[v1][v2];
}
//显示图对应的矩阵
public void showGraph(){
for (int[] i : edges){
System.out.println(Arrays.toString(i));
}
}
}
根据上面的逻辑可以很快写出添加节点和边的方法
注意:由于存在arraylist中,每个节点都有自己的下标。下面的v1,v2参数就是节点的下标。
//插入节点,每个节点都有下标
public void insert(String s){
vertexList.add(s);
}
/**
* 添加边
* @param v1 表示点的下标
* @param v2 v2 第二个顶点的下标
* @param weight 权值
*/
public void insertEdge(int v1, int v2, int weight){
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;//边的条数+1
}
遍历
图的遍历又分成深度遍历(DFS)和广度遍历(BFS)。
首先我们要实现下面两个方法。
第一个方法目的是获取邻接节点的下标,比如参数是0,代表获取a的第一个邻接节点下标也就是b。
第二个方法是获取邻接节点的下一个邻接节点坐标,比如参数是(0,1),则找到跳过(0,1)节点的(0,2),为邻接节点。
//得到第一个邻接节点下标
public int getFirstNeighbor(int index){
for (int j=0; j < vertexList.size(); j++){
if (edges[index][j] > 0)
return j;
}
return -1;
}
//根据前一个邻接节点的下标来获取下一个邻接节点
public int getNextNeighbor(int v1, int v2){
for (int j = v2 + 1; j < vertexList.size(); j++){
if (edges[v1][j] > 0)
return j;
}
return -1;
}
至此我们可以写出深度优先遍历。
1)从下标为i的节点开始,如果遍历输出过了该节点则把对应的bool数组置为true。
2)然后获取第一个邻接节点,如果存在则以此邻接节点继续递归。
3)如果访问过了该节点则跳过此节点并找到下一个邻接节点的下标。
4)第一个方法结束后仅仅是根据一个节点进行了DFS,所以需要重载此方法,对每个节点进行DFS
//深度优先遍历DFS
private void DFS(boolean[] isVisited,int i){
//访问该节点并输出
System.out.println(getValByIndex(i)+"->");
isVisited[i] = true;//标记为访问过
int j = getFirstNeighbor(i);//获取第一个邻接节点的下标
while (j != -1){
if ( !isVisited[j]){//如果j未被访问
DFS(isVisited,j);
}
//如果j已经被访问
j = getNextNeighbor(i,j);
}
}
//DFS的重载,遍历所有的节点
public void DFS(){
isVisited = new boolean[vertexList.size()];
for (int i=0; i < getNumOfVertex(); i++){
if (! isVisited[i]){
DFS(isVisited,i);
}
}
}
广度优先遍历则是另一种思路。
1)首先我们需要创建一个队列记录访问节点的顺序。并用u,w记录队列头节点的下标和邻接节点的下标。
2)当输出一个节点后则放入队列尾部。当队列不为空时从队列中删除并获得头节点的坐标,然后获取此节点的邻接节点。
3)如果存在且未被遍历过,则遍历此节点并放入队列尾部。若被遍历过则寻找下一个邻接节点。
//广度优先BFS,对一个节点
private void BFS(boolean[] isVisited,int i){
int u;//队列头节点的下标
int w;//邻接节点的下标
LinkedList queue = new LinkedList();//队列记录访问节点的顺序
System.out.println(getValByIndex(i)+"->");
isVisited[i] = true;
queue.addLast(i);
while (!queue.isEmpty()){
u = (int) queue.removeFirst();
w = getFirstNeighbor(u);
while (w != -1){
if (!isVisited[w]){
System.out.println(getValByIndex(w)+"->");
isVisited[w] = true;
queue.addLast(w);
}
w = getNextNeighbor(u,w);
}
}
}
//BFS的重载,遍历所有节点
public void BFS(){
isVisited = new boolean[vertexList.size()];
for (int i=0; i<getNumOfVertex(); i++){
if (!isVisited[i]){
BFS(isVisited,i);
}
}
}
深度优先遍历的思想是先根据y[0]进行遍历,寻找在y[0]行上的所有邻接节点并输出,结束后进行y[1],y[2],y[…]的遍历。
广度优先遍历则是根据y[0]找到邻接节点b后,立马根据b节点进行遍历。也就是未遍历完与a节点的所有邻接节点,立马换到y[1]行,找到b的邻接节点来遍历。同理也只会找到b的一个可遍历的邻接节点就将b放入队列,继续从f开始遍历。由于遍历过后会放入队列,找不到邻接节点后会回溯到队列中的头节点继续遍历。