图论基本概念
一、顶点(vertex)
带数字的点就是顶点,表示某个事物或对象。由于图的术语没有标准化,可以称顶点为点、节点、结点、端点等。
顶点的度=入度+出度
二、边(edge)
顶点之间的线条就是边,表示事物与事物之间的关系。 边表示的是顶点之间的逻辑关系
三、同构(Isomorphism )

从图(graph)的角度出发,这2个图是一样的,即它们是同构的。
这些图拥有相同的信息,是同一个图。同构的图区别仅在于画法不同。
四、有向/无向图(Directed Graph/ Undirected Graph)
最基本的图通常被定义为“无向图”,与之对应的则被称为“有向图”。两者唯一的区别在于,有向图中的边是有方向性的。 下图即是一个有向图,边的方向分别是:(1->2), (1-> 3), (3-> 1), (1->5), (2->3), (3->4), (3->5), (4->5), (1->6), (4->6)。要注意,图中的边(1->3)和(3->1)是不同的。有向图和无向图的许多原理和算法是相通的。
五、权重(weight)
边的权重(或者称为权值、开销、长度等),也是一个非常核心的概念,即每条边都有与之对应的值。
有时候为了应对特殊情况,边的权重可以是零或者负数。
六、路径/最短路径(path/shortest path)
在图上任取两顶点,分别作为起点(start vertex)和终点(end vertex),我们可以规划许多条由起点到终点的路线。不会来来回回绕圈子、不会重复经过同一个点和同一条边的路线,就是一条“路径”。两点之间存在路径,则称这2个顶点是连通的(connected)。
路径也有权重。路径经过的每一条边,沿路加权重,权重总和就是路径的权重(通常只加边的权重,而不考虑顶点的权重)。在路网中,路径的权重,可以想象成路径的总长度。在有向图中,路径还必须跟随边的方向。
值得注意的是,一条路径包含了顶点和边,因此路径本身也构成了图结构,只不过是一种特殊的图结构。
七、环(loop)
环,也成为环路,是一个与路径相似的概念。在路径的终点添加一条指向起点的边,就构成一条环路。通俗点说就是绕圈。
八、连通图/连通分量(connected graph/connected component)
如果在图G中,任意2个顶点之间都存在路径,那么称G为连通图。

上面这张图中,顶点8和顶点2之间就不存在路径,因此它不是一个连通图,当然该图中还有很多顶点之间不存在路径。
上图虽然不是一个连通图,但它有多个连通子图:1,2,3顶点构成一个连通子图,1,2,3,4,5顶点构成的子图是连通图,6,7,8,9顶点构成的子图也是连通图,当然还有很多子图。
一个图的最大连通子图称为它的连通分量。1,2,3,4,5顶点构成的子图就是该图的最大连通子图,也就是连通分量。
连通分量有如下特点:
- 是子图
- 子图是连通的
- 子图含有最大顶点数
注意:最大连通子图(连通分量)是其本身。
图的表示
// 图的接口public interface Graph {public int V();public int E();public void addEdge(int v, int w);boolean hasEdge(int v, int w);void show();public Iterable<Integer> adj(int v);}
1. 邻接矩阵
- 邻接矩阵表示无向图

- 邻接矩阵表示有向图


/*** 稠密图-邻接矩阵*/public class DenseGraph {private int n; // 节点数private int m; // 边数private boolean directed; // 是否为有向图private boolean[][] g; // 图的具体数据// 构造函数public DenseGraph( int n , boolean directed ){assert n >= 0;this.n = n;this.m = 0; // 初始化没有任何边this.directed = directed;// g初始化为n*n的布尔矩阵, 每一个g[i][j]均为false, 表示没有任和边// false为boolean型变量的默认值g = new boolean[n][n];}public int V(){ return n;} // 返回节点个数public int E(){ return m;} // 返回边的个数// 向图中添加一个边public void addEdge( int v , int w ){assert v >= 0 && v < n ;assert w >= 0 && w < n ;if( hasEdge( v , w ) ){return;}g[v][w] = true;if( !directed ){g[w][v] = true;}m ++;}// 验证图中是否有从v到w的边boolean hasEdge( int v , int w ){assert v >= 0 && v < n ;assert w >= 0 && w < n ;return g[v][w];}//遍历邻边//返回图中一个顶点的所有相邻顶点public Iterable<Integer> adj(int v) {assert v >= 0 && v < n;Vector<Integer> adjV = new Vector<Integer>();for(int i = 0 ; i < n ; i ++ )if( g[v][i] )adjV.add(i);return adjV;}}
2. 邻接表
- 邻接表表示无向图

- 邻接表表示有向图



/*** 稀松图-领接表*/public class SparseGraph {private int n; // 节点数private int m; // 边数private boolean directed; // 是否为有向图private Vector<Integer>[] g; // 图的具体数据// 构造函数public SparseGraph( int n , boolean directed ){assert n >= 0;this.n = n;this.m = 0; // 初始化没有任何边this.directed = directed;// g初始化为n个空的vector, 表示每一个g[i]都为空, 即没有任和边g = (Vector<Integer>[])new Vector[n];for(int i = 0 ; i < n ; i ++){g[i] = new Vector<Integer>();}}public int V(){ return n;} // 返回节点个数public int E(){ return m;} // 返回边的个数// 向图中添加一个边public void addEdge( int v, int w ){assert v >= 0 && v < n ;assert w >= 0 && w < n ;g[v].add(w);if( v != w && !directed ){g[w].add(v);}m ++;}// 验证图中是否有从v到w的边 -->时间复杂度:O(n)boolean hasEdge( int v , int w ){assert v >= 0 && v < n ;assert w >= 0 && w < n ;for( int i = 0 ; i < g[v].size() ; i ++ ){if( g[v].elementAt(i) == w ){return true;}}return false;}//遍历邻边//返回图中一个顶点的所有相邻顶点public Iterable<Integer> adj(int v){assert v>=0 && v<n;return g[v];}}
图的深度优先遍历(DFS)
深度优先搜索(DFS):从某个顶点v0出发,访问此顶点,然后依次从v0的未被访问的邻接点出发递归地进行同样的DFS,直至图中和v0有路径相通的顶点都被访问,若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。
/*** 图的深度优先遍历*/public class DepthFirstPaths {//标记顶点是否被访问过private boolean[] visited;//记录路径private int[] edgeTo;//起点private int s;public DepthFirstPaths(Graph graph,int s){visited=new boolean[graph.V()];edgeTo=new int[graph.V()];for(int i=0;i<graph.V();i++){edgeTo[i]=-1;}this.s=s;dfs(graph,this.s);}//深度优先搜索private void dfs(Graph graph,int v){visited[v]=true;for(int w:graph.adj(v)){if(!visited[w]){edgeTo[w]=v; //w--->v的路径dfs(graph,w);}}}//是否有到顶点v的路径private boolean hasPathTo(int v){return visited[v];}//获取从 s->v 的路径public Iterable<Integer> pathTo(int v){if(!hasPathTo(v)){return null;}Stack<Integer> path=new Stack<>();for(int i=v;i!=s;i=edgeTo[i]){path.push(i);}path.push(s);return path;}public void showPath(int v){Stack<Integer> path= (Stack<Integer>) pathTo(v);StringBuffer sb=new StringBuffer();sb.append("[");while(!path.isEmpty()){int w=path.peek();if(path.size()==1){sb.append(w);path.pop();}else{sb.append(w+"-->");path.pop();}}sb.append("]");System.out.println(sb.toString());}}
时间复杂度:
- 领接表:O(V+E)
- 邻接矩阵:O(V^2)
统计图的连通分量
/*** 图的dfs应用之统计图的连通分量*/public class Component {private Graph graph;//连通分量的个数private int num;private boolean[] visited;public Component(Graph graph){this.graph=graph;visited=new boolean[graph.V()];num=0;for(int i=0;i<graph.V();i++){if(!visited[i]){dfs(i);num++;}}}private void dfs(int v){visited[v]=true;for(int w:graph.adj(v)){if(!visited[w]){dfs(w);}}}//获取连通分量个数public int getNum(){return num;}}
图的广度优先遍历(BFS)
广度优先搜索(BFS):从某个顶点v0出发,访问此顶点之后依次访问v0的各个未访问的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到,若此时图中尚有顶点未被访问,则从另一个未被访问的顶点作为起点,重复上述过程,直到图中所有顶点均被访问过为止。
/*** 图的广度优先遍历*/public class BreadthFirstPaths {//标记顶点是否被访问过private boolean[] visited;//记录路径private int[] edgeTo;//起点private int s;public BreadthFirstPaths(Graph graph, int s){visited=new boolean[graph.V()];edgeTo=new int[graph.V()];for(int i=0;i<graph.V();i++){edgeTo[i]=-1;}this.s=s;bfs(graph,this.s);}//广度优先搜索private void bfs(Graph graph,int s){visited[s]=true;Queue<Integer> queue=new LinkedList<>();queue.add(s);while (!queue.isEmpty()){int v=queue.poll();for(int w:graph.adj(v)){if(!visited[w]){visited[w]=true;edgeTo[w]=v;queue.add(w);}}}}//是否有到顶点v的路径private boolean hasPathTo(int v){return visited[v];}//获取从 s->v 的路径public Iterable<Integer> pathTo(int v){if(!hasPathTo(v)){return null;}Stack<Integer> path=new Stack<>();for(int i=v;i!=s;i=edgeTo[i]){path.push(i);}path.push(s);return path;}public void showPath(int v){Stack<Integer> path= (Stack<Integer>) pathTo(v);StringBuffer sb=new StringBuffer();sb.append("[");while(!path.isEmpty()){int w=path.peek();if(path.size()==1){sb.append(w);path.pop();}else{sb.append(w+"-->");path.pop();}}sb.append("]");System.out.println(sb.toString());}}
