图的基本术语:
图结构中顶点具有多对多的关系,有有向图和无向图的区分,有带权图和无权图的区分。
QQ的好友双方中,一方删除好友,另一方的好友也会被删除,可以认为QQ的好友关系是一种无向图;
微信的好友双方中,一方删除好友,另一方好友并不会被删除,可以认为微信的好友关系是一种有向图。
图的存储
邻接矩阵
存储结构:
图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息(有权图的二维数组存储权重,0表示两顶点无关系;无权图的二维数组存储0,1表示两顶点无 / 有关系)。
无向图的二维数组是一个对称矩阵,因为在无向图中,V0和V1有关联,那么V1和V0也必定有关联,因此Arr[0][1]和Arr[1][0]的值一定相等;有向图的二维数组则不满足这个规律。
邻接矩阵的缺点:
边信息的存储占用了太多的空间。试想,如果一个图有1000个顶点,其中只有10个顶点之间有关联(这种情况叫做稀疏图),却不得不建立一个1000X1000的二维数组,浪费了极大的存储空间。
邻接表
对于边数相对顶点较少的图,采用邻接表存储
存储结构:
图的所有顶点用链表或数组存储,后继节点所存储的是该顶点能够直接达到的相邻顶点
邻接表的优缺点:
邻接表对于查找两个节点是否能够到达是非常容易的,但如果要查找与顶点A相差”一步“的顶点时,就需要遍历所有邻接表的顶点,这时候就需要使用逆邻接表。
逆邻接表
存储结构:
图的所有顶点用链表或数组存储,后继节点所存储的是能够直接达到该顶点的相邻顶点(与邻接表相反)。
十字链表
存储结构:
结合了邻接链表和逆邻接链表
图结构的存储图解参考
图的遍历
遍历图中每一个顶点,且每一个顶点都只能被遍历一次
广度优先遍历(BFS,breadth first search)
使用List中栈的特性完成遍历
/**
* 广度优先遍历
*/
public static void breadthFS(Graph graph, int start, boolean[] visited, LinkedList<Integer> queue) {
queue.offer(start);//入栈当前下标
while (!queue.isEmpty()) {
int front = queue.poll();//弹出栈顶下标
if (visited[front]) {
continue;//重新执行while循环
}
System.out.println(graph.vertexes[front].data);
visited[front] = true;
for (int index : graph.adj[front]) {
queue.offer(index);
}
}
}
深度优先遍历(DFS,depth first search)
类似于树的前序遍历,在dfs中,不断的向下访问,然后使用递归方式回溯之前没有被遍历到的顶点
代码如下:
/**
* 深度优先遍历
*/
public static void depthFS(Graph graph, int start, boolean[] visited) {
System.out.println(graph.vertexes[start].data);
visited[start] = true;//标记start位置元素已被访问
for (int index : graph.adj[start]) {
if (!visited[index]) {
depthFS(graph, index, visited);
}
}
}
邻接表的dfs / bfs遍历代码:
import java.util.LinkedList;
/**
* 图的dfs / bfs遍历
*/
public class Graph_dfsAndBfs {
public static void main(String[] args) {
Graph graph = new Graph(6);
graph.adj[0].add(1);
graph.adj[0].add(2);
graph.adj[0].add(3);
graph.adj[1].add(0);
graph.adj[1].add(3);
graph.adj[1].add(4);
graph.adj[2].add(0);
graph.adj[3].add(0);
graph.adj[3].add(1);
graph.adj[3].add(4);
graph.adj[3].add(5);
graph.adj[4].add(1);
graph.adj[4].add(3);
graph.adj[4].add(5);
graph.adj[5].add(3);
graph.adj[5].add(4);
System.out.println("图的深度优先遍历:");
depthFS(graph, 0, new boolean[graph.size]);
System.out.println("图的广度优先遍历:");
breadthFS(graph, 0, new boolean[graph.size], new LinkedList<Integer>());
}
/**
* 广度优先遍历
*/
public static void breadthFS(Graph graph, int start, boolean[] visited, LinkedList<Integer> queue) {
queue.offer(start);//入栈当前下标
while (!queue.isEmpty()) {
int front = queue.poll();//弹出栈顶下标
if (visited[front]) {
continue;//重新执行while循环
}
System.out.println(graph.vertexes[front].data);
visited[front] = true;
for (int index : graph.adj[front]) {
queue.offer(index);
}
}
}
/**
* 深度优先遍历
*/
public static void depthFS(Graph graph, int start, boolean[] visited) {
System.out.println(graph.vertexes[start].data);
visited[start] = true;//标记start位置元素已被访问
for (int index : graph.adj[start]) {
if (!visited[index]) {
depthFS(graph, index, visited);
}
}
}
/**
* 图的顶点类
*/
static class Vertex {
int data;
Vertex(int data) {
this.data = data;
}
}
/**
* 图的存储(邻接表形式)
*/
static class Graph {
private int size;
private Vertex[] vertexes;//存储每个顶点的数组
private LinkedList<Integer> adj[];//每个顶点后的后继节点
Graph(int size) {
this.size = size;
//初始化顶点和邻接矩阵
vertexes = new Vertex[size];
adj = new LinkedList[size];
for (int i = 0; i < size; i++) {
vertexes[i] = new Vertex(i);
adj[i] = new LinkedList();
}
}
}
}