图论
图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系
1.1图的重要组成部分
节点:Vertex
边:Edge
可以表示:交通运输、社交网络、互联网、工作安排、脑区活动、程序状态执行
1.2图的分类
- 无向图
- 有向图
- 无权图
- 有权图
1.2图的表示
链接矩阵,用矩阵的方式表示图
- 无向图的表示
- 有向图的表示
领接表,类似哈希表
- 无向图的表示
- 有向图的表示
Graph接口的定义
public interface Graph {
//获取节点个数
public int V();
//获取边的个数
public int E();
//向图中添加一条边v-w 或 v->w
public void addEdge(int v,int w);
//判断图中两个节点之间是否有边(非连通,是直连)
public boolean hasEdge(int v,int w);
//打印图的内容
public void show();
//返回一个图中一个节点所有的邻边
public Iterable<Integer> adj(int v);
}
DenseGraph类的定义(邻接矩阵-稠密图)
import java.util.Vector;
public class DenseGraph implements Graph{
private boolean[][] g;//图的具体数据
private int n;//节点的个数
private int m;//边的个数
private boolean directed;//是否为有向图 true为有向图,false为无向图
public DenseGraph(int n,boolean directed) {
if(n<0){
throw new IllegalArgumentException("图中的节点个数必须大于等于0");
}
this.n=n;
this.directed=directed;
this.m=0;
this.g=new boolean[n][n];
}
public DenseGraph() {
this(0,false);
}
@Override
public int V() {
return this.n;
}
@Override
public int E() {
return this.m;
}
@Override
public void addEdge(int v, int w) {
if(v<0||v>=n){
throw new IllegalArgumentException(v+"节点不存在");
}
if(w<0||w>=n){
throw new IllegalArgumentException(w+"节点不存在");
}
//不考虑平行边和自环边
//考虑有向和无向
if(v==w){ //防止自环边
return;
}
if(hasEdge(v,w)){//如果已存在边,则不加入
return;
}
g[v][w]=true;//有向图,单向
if(!directed){
g[w][v]=true;//无向图,双向
}
m++;
}
@Override
public boolean hasEdge(int v, int w) {
if(v<0||v>=n){
throw new IllegalArgumentException(v+"节点不存在");
}
if(w<0||w>=n){
throw new IllegalArgumentException(w+"节点不存在");
}
return g[v][w];
}
@Override
public void show() {
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
System.out.print((g[i][j]==true? 1:0)+" ");
}
System.out.println();
}
}
@Override
public Iterable<Integer> adj(int v) {
if(v<0||v>=n){
throw new IllegalArgumentException(v+"节点不存在");
}
Vector<Integer> adjv=new Vector<Integer>();
for(int i=0;i<n;i++){
if(g[v][i]){
adjv.add(i);
}
}
return adjv;
}
}
SparesGraph类的定义(邻接表–稀疏图)
import java.util.Vector;
//邻接表--稀疏图
public class SparesGraph implements Graph{
private int n;
private int m;
private boolean directed;
private Vector<Integer>[] g;
public SparesGraph(int n,boolean directed) {
if(n<0){
throw new IllegalArgumentException("节点个数必须大于等于0");
}
this.n=n;
this.m=0;
this.directed=directed;
this.g=(Vector<Integer>[])new Vector[n];
for(int i=0;i<n;i++){
g[i]=new Vector<Integer>();
}
}
@Override
public int V() {
return this.n;
}
@Override
public int E() {
return this.m;
}
@Override
public void addEdge(int v, int w) {
if(v<0||v>=n){
throw new IllegalArgumentException(v+"节点不存在");
}
if(w<0||w>=n){
throw new IllegalArgumentException(w+"节点不存在");
}
if(v==w){ //防止自环边
return;
}
if(hasEdge(v, w)){
return;
}
g[v].add(w);
if(!directed){
g[w].add(v);
}
m++;
}
@Override
public boolean hasEdge(int v, int w) {
if(v<0||v>=n){
throw new IllegalArgumentException(v+"节点不存在");
}
if(w<0||w>=n){
throw new IllegalArgumentException(w+"节点不存在");
}
return g[v].contains(w);
}
@Override
public void show() {
for(int i=0;i<n;i++){
System.out.print("vertext"+i+":");
for(int j=0;j<g[i].size();j++){
System.out.print(g[i].get(j)+" ");
}
System.out.println();
}
}
@Override
public Iterable<Integer> adj(int v) {
return g[v];
}
}
深度优先遍历
深度优先搜索,是图论中的经典算法。其利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。
- 过程
设x是当前被访问顶点,在对x做过访问标记后,选择一条从x出发的未检测过的边(x,y)。若发现顶点y已访问过,则重新选择另一条从x出发的未检测过的边,否则沿边(x,y)到达未曾访问过的y,对y访问并将其标记为已访问过;然后从y开始搜索,直到搜索完从y出发的所有路径,即访问完所有从y出发可达的顶点之后,才回溯到顶点x,并且再选择一条从x出发的未检测过的边。上述过程直至从x出发的所有边都已检测过为止。此时,若x不是源点,则回溯到在x之前被访问过的顶点;否则图中所有和源点有路径相通的顶点(即从源点可达的所有顶点)都已被访问过,若图G是连通图,则遍历过程结束,否则继续选择一个尚未被访问的顶点作为新的顶点,继续遍历。
- 源码
//深度优先遍历
public class Components {
private Graph G;
private boolean[] visited;
private int ccount;
private int[] id;
private StringBuffer path;
public Components(Graph graph) {
path=new StringBuffer();
this.G=graph;
visited=new boolean[G.V()];//false
id=new int[G.V()];//0
ccount=0;
for (int i = 0; i < G.V(); i++) {
visited[i]=false;
id[i]=-1;
}
for (int i = 0; i < G.V(); i++) {
if(!visited[i]){
dfs(i);
path.append("\n");
ccount++;
}
}
}
private void dfs(int v) {
visited[v]=true; //标记已经被访问
id[v]=ccount;
path.append(v+"->");
for (int i : G.adj(v)) {
if(!visited[i]){
dfs(i);
}
}
}
public int ccount(){
return ccount;
}
public boolean isConnected(int v,int w){
if(v<0||v>=G.V()){
throw new IllegalArgumentException(v+"节点不存在");
}
if(w<0||w>=G.V()){
throw new IllegalArgumentException(w+"节点不存在");
}
return id[v]==id[w];
}
public String getDFSPath(){
return path.toString();
}
}
广度优先遍历(无权图的最短路径)
广度优先遍历是连通图的一种遍历策略。因为它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域,故得名。
- 过程
1、从图中某个顶点V0出发,并访问此顶点;
2、从V0出发,访问V0的各个未曾访问的邻接点W1,W2,…,Wk;然后,依次从W1,W2,…,Wk出发访问各自未被访问的邻接点;
3、重复步骤2,直到全部顶点都被访问为止。
- 源码
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import java.util.Vector;
//无权图的最短路径--广度优先遍历
public class ShortestPath {
private Graph G;
private int s;
private boolean[] visited;
private int[] from;
private int[] ord;
public ShortestPath(Graph graph,int s) {
this.G=graph;
if(s<0||s>G.V()){
throw new IllegalArgumentException("节点不存在");
}
visited=new boolean[G.V()];
from=new int[G.V()];
ord=new int[G.V()];
for(int i=0;i<G.V();i++){
visited[i]=false;
from[i]=-1;
ord[i]=-1;
}
this.s=s;
Queue<Integer> queue=new LinkedList<Integer>();
queue.add(s);
visited[s]=true;
ord[s]=0;
while(!queue.isEmpty()){
int v=queue.remove();
for(int i:G.adj(v)){
if(!visited[i]){
queue.add(i);
visited[i]=true;
from[i]=v;
ord[i]=ord[v]+1;
}
}
}
}
public boolean hasPath(int w){
if(w<0||w>=G.V()){
throw new IllegalArgumentException("节点不存在");
}
return visited[w]&&ord[w]!=-1;
}
public Vector<Integer> path(int w){
if(!hasPath(w)){
throw new IllegalArgumentException("此路不通");
}
Stack<Integer> stack=new Stack<>();
int p=w;
while(p!=-1){
stack.push(p);
p=from[p];
}
Vector<Integer> vect=new Vector<Integer>();
while(!stack.isEmpty()){
vect.add(stack.pop());
}
return vect;
}
public void showPath(int w){
if(!hasPath(w)){
throw new IllegalArgumentException("此路不通");
}
Vector<Integer> vect=path(w);
for(int i=0;i<vect.size();i++){
System.out.print(vect.get(i));
if(i==vect.size()-1){
System.out.println();
}else{
System.out.print("->");
}
}
}
public int length(int w){
if(w<0||w>=G.V()){
throw new IllegalArgumentException("节点不存在");
}
return ord[w];
}
}
找寻从V-W之间的路径
import java.util.Stack;
import java.util.Vector;
import javax.swing.plaf.synth.SynthSpinnerUI;
public class Path {
private Graph G;
private int s; //起点
private boolean[] visited;
private int[] from; //每个节点的来源 开始都是-1
//在图graph中,以s为起点开始查路径
public Path(Graph graph,int s) {
this.G=graph;
if(s<0||s>=G.V()){
throw new IllegalArgumentException("节点不存在");
}
visited=new boolean[G.V()];
from=new int[G.V()];
for(int i=0;i<G.V();i++){
visited[i]=false;
from[i]=-1;
}
this.s=s;
dfs(s);
}
private void dfs(int v) {
visited[v]=true;
for(int i:G.adj(v)){
if(!visited[i]){
from[i]=v;
dfs(i);
}
}
}
//验证从v到w是否有路径
public boolean hasPath(int w){
if(w<0||w>=G.V()){
throw new IllegalArgumentException("节点不存在");
}
return visited[w];
}
//返回从v-w的所经过节点的路径
public Vector<Integer> path(int w){
if(!hasPath(w)){
throw new IllegalArgumentException("此路不通");
}
Stack<Integer> stack=new Stack<>();
int p=w;
while(p!=-1){
stack.push(p);
p=from[p];
}
Vector<Integer> vect=new Vector<>();
while(!stack.isEmpty()){
vect.add(stack.pop());
}
return vect;
}
//打印从v-w之间的路径
public void showPath(int w){
if(!hasPath(w)){
throw new IllegalArgumentException("此路不通");
}
Vector<Integer> vect=path(w);
for(int i=0;i<vect.size();i++){
System.out.print(vect.get(i));
if(i==vect.size()-1){
System.out.println();
}else{
System.out.print("->");
}
}
}
}