数据结构之图(Graph)
图的概述
什么是图
如图就是一张图,其实之前介绍的树、链表都可以看做一个简单的图。
图描述的是一种多对多的关系,由顶点(vertex)和连接顶点间的边(edge)组成。每个顶点可以有零个或多个前驱、也可以有零个或多个后继。
注:图可以没有边,但至少有一个顶点。
因此图可以表示成G=(V,E)。V即顶点的集合、E为边的集合。
G = (V,E)。 其中,VE={<v,w>|v,w\(\in\)V且P(v,w)}。
<v,w>是从v到w的一条边,有方向的,与<w,v>不一样的。P(v,w)是定义在<v,w>上的权值信息(即赋予的有意义的数值或信息)。
图的定义及术语
几种常见的图
- 无向图:图中的边没有方向。
- 有向图:图中的边存在方向。
- 完全图:图中任意两个顶点都有边连接。
- 无权图:边上没有赋值,即不带权值。
- 有权图:边上附有权值。
顶点与边的数目关系
假使图中有n个顶点,e条边。
若有e=n(n-1)/2条无向边,则是无向完全图。
若有e=n(n-1)条有向边,则是有向完全图。
若e<nlogn则称作稀疏图,否则为稠密图。
图的一些概念
- 邻接点:若顶点v和w之间存在边,则v、w互为邻接点。
- 度:与顶点v关联的边的数目,即是度。
- 出度/入度:对于有向图,<v,w>是v指向w的一条边,那么该边是v的出边,w的入边。
出度/入度即是顶点的出边/入边数目。
所以顶点的度=出度+入度。 - 路径长度:从v到w之间存在一条路径,路径上边的数目即是路径的长度。
- 简单路径:即路径中顶点不重复的路径。
- 连通图:若G(图)中,任意两个顶点之间都有路径连通,则G称为连通图。
- 强连通图:对于有向图中,任意两点之间都有一条有向路径。则称这个有向图为强连通图。
- 连通分量:非连通图,其中的极大连通子图称为连通分量。这里的极大是指子图中包含的顶点个数极大。
- 生成树:假使一个连通图,n个顶点e条边,若n-1条边与n个顶点构成的极小连通子图,则这个极小连通子图称为该连通图的生成树。如下面的连通图,红色部分即以D为根的生成树。
- 生成森林:非连通图,每个连通分量的生成树 共同构成该非连通图的生成森林。
图的存储
邻接矩阵
如下图,右边的就是左边图的邻接矩阵表示。即记录每个顶点与其他所有点是否存在边,存在则标1,否则为0。
标志信息也可以根据实际情况表示,如权值等。
无向图该矩阵是对称的,有向图则不一定。
邻接表
如下图所示,每个顶点指向了一个链表,链表里记录了所有存在边的顶点在数组中的位置。
图的遍历
图的遍历都以这个有向图为例进行说明的。
深度优先(DFS:Depth First Search)
从图中某个顶点V0出发,访问V0,然后依次以v0相连的顶点为出发点向后访问。依次类推,直到所有与V0顶点相连的所有结点都被访问到。 如果图中仍有未访问的顶点,则该图为非连通图,在图中未访问的顶点选择一个顶点为起点,并重复上述过程,直到访问完图中的所有顶点为止。
下面分别是使用邻接矩阵和邻接表表示的图 实现的深度优先的demo,可以大致看看了解下。
邻接矩阵表示的图
注意,邻接矩阵表示 使用的非递归方法实现,通过栈来实现的
import java.util.ArrayList;
import java.util.Stack;
//深度优先:使用邻接矩阵存储方式
public class DFSArrayTest {
static ArrayList<Node> nodes = new ArrayList<>();
static class Node {
char data;//数据
boolean visited;//是否访问过标志
Node(char data) {
this.data = data;
}
}
//查找某顶点在邻接矩阵中的邻接点
public ArrayList<Node> findNeighbours(int adjacency_matrix[][], Node x) {
int nodeIndex = -1;
ArrayList<Node> neighbours = new ArrayList<>();
for (int i = 0; i < nodes.size(); i++) {
if (nodes.get(i).equals(x)) {
nodeIndex = i;
break;
}
}
if (nodeIndex != -1) {
for (int j = 0; j < adjacency_matrix[nodeIndex].length; j++) {
if (adjacency_matrix[nodeIndex][j] == 1) {
neighbours.add(nodes.get(j));
}
}
}
return neighbours;
}
//使用栈实现深度优先
public void dfsUseStack(int adjacency_matrix[][], Node node) {
Stack<Node> stack = new Stack<>();
stack.add(node);
while (!stack.isEmpty()) {
Node element = stack.pop();
if (!element.visited) {
System.out.print(element.data + " ");
element.visited = true;
}
ArrayList<Node> neighbours = findNeighbours(adjacency_matrix, element);
for (int i = 0; i < neighbours.size(); i++) {
Node n = neighbours.get(i);
if (n != null && !n.visited) {
stack.add(n);
}
}
}
}
public static void clearVisitedFlags() {
for (int i = 0; i < nodes.size(); i++) {
nodes.get(i).visited = false;
}
}
public static void main(String arg[]) {
Node nodeA = new Node('A');
Node nodeB = new Node('B');
Node nodeC = new Node('C');
Node nodeD = new Node('D');
Node nodeE = new Node('E');
int adjacency_matrix[][] = {
//A B C D E
{ 0, 1, 0, 0, 1 }, // A
{ 0, 0, 0, 1, 0 }, // B
{ 1, 0, 0, 0, 0 }, // C
{ 0, 0, 1, 0, 0 }, // D
{ 0, 0, 0, 1, 0 } // E
};
nodes.add(nodeA);nodes.add(nodeB);nodes.add(nodeC);nodes.add(nodeD);nodes.add(nodeE);
DFSArrayTest dfsArrayTest = new DFSArrayTest();
System.out.println("DFSArray(以C为开始顶点):");
dfsArrayTest.dfsUseStack(adjacency_matrix, nodeC);
System.out.println();
clearVisitedFlags();
System.out.println("DFSArray(以A为开始顶点):");
dfsArrayTest.dfsUseStack(adjacency_matrix, nodeA);
}
}
结果如下:
DFSArray(以C为开始顶点):
C A E D B
DFSArray(以A为开始顶点):
A E D C B
邻接表 表示的图
注意,邻接表表示 使用的是递归方法实现
import java.util.ArrayList;
import java.util.LinkedList;
//深度优先:使用邻接表存储方式
class DFSLinkedTest {
static class Node {
char data;//数据
boolean visited;//是否访问过的标志
LinkedList<Node> neighbours;//所有邻接点
Node(char data) {
this.data = data;
this.neighbours = new LinkedList<>();
}
}
//深度遍历,node为开始顶点
private void dfsUtil(Node node) {
node.visited = true;
System.out.print(node.data + " ");
for (int i = 0; i < node.neighbours.size(); i++) {
Node neighbourNode = node.neighbours.get(i);
if (neighbourNode != null && !neighbourNode.visited) {
dfsUtil(neighbourNode);
}
}
}
//恢复所有顶点标志到未访问
private void clearVisitedFlags(ArrayList<Node> nodes) {
for (int i = 0; i < nodes.size(); i++) {
nodes.get(i).visited = false;
}
}
public static void main(String args[]) {
DFSLinkedTest dfsLinkedTest = new DFSLinkedTest();
ArrayList<Node> nodes = new ArrayList<>();
Node nodeA = new Node('A');
Node nodeB = new Node('B');
Node nodeC = new Node('C');
Node nodeD = new Node('D');
Node nodeE = new Node('E');
nodeA.neighbours.add(nodeB);
nodeA.neighbours.add(nodeE);
nodeB.neighbours.add(nodeD);
nodeC.neighbours.add(nodeA);
nodeD.neighbours.add(nodeC);
nodeE.neighbours.add(nodeD);
nodes.add(nodeA);nodes.add(nodeB);nodes.add(nodeC);nodes.add(nodeD);nodes.add(nodeE);
dfsLinkedTest.clearVisitedFlags(nodes);
System.out.println("DFSLinked(以C为开始顶点):");
dfsLinkedTest.dfsUtil(nodeC);
System.out.println();
dfsLinkedTest.clearVisitedFlags(nodes);
System.out.println("DFSLinked(以A为开始顶点):");
dfsLinkedTest.dfsUtil(nodeA);
}
}
结果如下:
DFSLinked(以C为开始顶点):
C A B D E
DFSLinked(以A为开始顶点):
A B D C E
广度优先(BFS:Breadth First Search)
从图中的某个顶点V0开始,访问V0,然后依次访问V0的所有未访问的邻接点,然后以访问这些顶点的顺序访问它们的邻接点,直到所有顶点在都被访问到。如果图中仍有未访问的顶点,则该图为非连通图,在图中未访问的顶点选择一个顶点为起点,并重复上述过程,直到访问完图中的所有顶点为止。
同样 下面分别是使用邻接矩阵和邻接表表示的图 实现的广度优先的demo,可以大致看看了解下。
邻接矩阵表示的图
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
//广度优先:使用邻接矩阵存储方式
public class BFSArrayTest {
private Queue<Node> queue;
static ArrayList<Node> nodes = new ArrayList<Node>();
//顶点定义
static class Node {
char data;
boolean visited;
Node(char data) {
this.data = data;
}
}
public BFSArrayTest() {
queue = new LinkedList<Node>();
}
//查找某顶点在邻接矩阵中的邻接点
public ArrayList<Node> findNeighbours(int adjacency_matrix[][], Node x) {
int nodeIndex = -1;
ArrayList<Node> neighbours = new ArrayList<Node>();
for (int i = 0; i < nodes.size(); i++) {
if (nodes.get(i).equals(x)) {
nodeIndex = i;
break;
}
}
if (nodeIndex != -1) {
for (int j = 0; j < adjacency_matrix[nodeIndex].length; j++) {
if (adjacency_matrix[nodeIndex][j] == 1) {
neighbours.add(nodes.get(j));
}
}
}
return neighbours;
}
public void bfsUtil(int adjacency_matrix[][], Node node) {
queue.add(node);
node.visited = true;
while (!queue.isEmpty()) {
Node element = queue.remove();
System.out.print(element.data + " ");
ArrayList<Node> neighbours = findNeighbours(adjacency_matrix, element);
for (int i = 0; i < neighbours.size(); i++) {
Node n = neighbours.get(i);
if (n != null && !n.visited) {
queue.add(n);
n.visited = true;
}
}
}
}
public void clearVisitedFlags() {
for (int i = 0; i < nodes.size(); i++) {
nodes.get(i).visited = false;
}
}
public static void main(String arg[]) {
Node nodeA = new Node('A');
Node nodeB = new Node('B');
Node nodeC = new Node('C');
Node nodeD = new Node('D');
Node nodeE = new Node('E');
int adjacency_matrix[][] = {
//A B C D E
{ 0, 1, 0, 0, 1 }, // A
{ 0, 0, 0, 1, 0 }, // B
{ 1, 0, 0, 0, 0 }, // C
{ 0, 0, 1, 0, 0 }, // D
{ 0, 0, 0, 1, 0 } // E
};
nodes.add(nodeA);nodes.add(nodeB);nodes.add(nodeC);nodes.add(nodeD);nodes.add(nodeE);
BFSArrayTest bfsArrayTest = new BFSArrayTest();
System.out.println("BFSArray(以C为开始顶点):");
bfsArrayTest.bfsUtil(adjacency_matrix, nodeC);
System.out.println();
bfsArrayTest.clearVisitedFlags();
System.out.println("BFSArray(以A为开始顶点):");
bfsArrayTest.bfsUtil(adjacency_matrix, nodeA);
}
}
结果如下:
BFSArray(以C为开始顶点):
C A B E D
BFSArray(以A为开始顶点):
A B E D C
邻接表表示的图
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
//广度优先:使用邻接表存储方式
public class BFSLinkedTest {
private Queue<Node> queue;
static ArrayList<Node> nodes = new ArrayList<Node>();
static class Node {
char data;
boolean visited;
LinkedList<Node> neighbours;
Node(char data) {
this.data = data;
this.neighbours = new LinkedList<>();
}
}
public BFSLinkedTest() {
queue = new LinkedList<Node>();
}
public void bfsUtil(Node node) {
queue.add(node);
node.visited = true;
while (!queue.isEmpty()) {
Node element = queue.remove();
System.out.print(element.data + " ");
for (int i = 0; i < element.neighbours.size(); i++) {
Node n = element.neighbours.get(i);
if (n != null && !n.visited) {
queue.add(n);
n.visited = true;
}
}
}
}
private void clearVisitedFlags(ArrayList<Node> nodes) {
for (int i = 0; i < nodes.size(); i++) {
nodes.get(i).visited = false;
}
}
public static void main(String arg[]) {
ArrayList<Node> nodes = new ArrayList<>();
Node nodeA = new Node('A');
Node nodeB = new Node('B');
Node nodeC = new Node('C');
Node nodeD = new Node('D');
Node nodeE = new Node('E');
nodeA.neighbours.add(nodeB);
nodeA.neighbours.add(nodeE);
nodeB.neighbours.add(nodeD);
nodeC.neighbours.add(nodeA);
nodeD.neighbours.add(nodeC);
nodeE.neighbours.add(nodeD);
BFSLinkedTest bfsLinkedTest = new BFSLinkedTest();
nodes.add(nodeA);nodes.add(nodeB);nodes.add(nodeC);nodes.add(nodeD);nodes.add(nodeE);
bfsLinkedTest.clearVisitedFlags(nodes);
System.out.println("BFSLinked(以C为开始顶点):");
bfsLinkedTest.bfsUtil(nodeC);
System.out.println();
bfsLinkedTest.clearVisitedFlags(nodes);
System.out.println("BFSLinked(以A为开始顶点):");
bfsLinkedTest.bfsUtil(nodeA);
}
}
结果如下:
BFSLinked(以C为开始顶点):
C A B E D
BFSLinked(以A为开始顶点):
A B E D C