图论算法的简单入门

1.图的定义

图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为G(V,E),其中G表示一个图,V是图G的顶点的集合,E 是图G中边的集合;线性表中我们把数据元素叫做元素,树中的元素叫做结点,在图中数据元素我们称为顶点(Vertex)。在图中,若V是顶点的集合,则V非空且有穷;如下图就是一个有穷非空图:
在这里插入图片描述

1.1.各种图定义

1.1.1.

若顶点Vi到Vj之间的边没有方向,则称这条边为无向边;如果图中的任意两个顶点之间的边都是无向边,那么这个图就是无向图;反之则成为有向图;如图2中连接顶点A到D的有向边就是弧,A是弧尾,D是弧头,<A,D>表示弧;
在这里插入图片描述

1.1.2.

在图中,若存在顶点到其自身的边,且同一条边不重复出现,则这样的图叫做简单图;在无向图中,如果任意两个顶点之间都存在边,那么称该图为完全无向图;含有n个顶点的完全无向图含有
(n*(n-1))/2条边;
在有向图中,如果任意两个顶点之间都存在方向相反的两条边,则称该图为完全有向图;
在这里插入图片描述

1.1.3.

有很少条边或弧的图称为稀疏图,反之称为稠密图;与图的边或弧相关的数字叫做权,这些权可以表示从一个顶点到另一个顶点的距离或者耗费。这种带权的图通常称为网;如图就一张带权图:
在这里插入图片描述
假设有两个图G1=(V1,{E1})和G2={V2,{E2}},如果V2∈V1,且E2∈E1,则称G2是G1的子图;下图为有向图和无向图及其子图;
在这里插入图片描述

1.2.连通图的相关术语

在无向图G中,如果顶点V1到V2有路径,则称V1和V2是连同的。如果对于图中的任意两个顶点V和V’都是连同的,则称图G为连通图;
无向图中的极大连同子图称为连同分量;通俗点讲就是有几个子图连图分量就是几;
所谓一个连通图的生成树是一个极小连通子图,它包含图中全部的n个顶点,但只有足以构成一棵树的n-1条边,如果他有大于n-1条边,那么他一定是一个环;
在这里插入图片描述如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1,则是一颗有向树;一个有向图的生成森林由若干个有向树组成,含有图中的全部顶点,但只有足以构成若干棵不相交的有向树的弧;

1.3.图的基本操作

public interface Graph {
	public int V();//获取结点的个数
	public int E();//获取边的个数
	public void addEdge(int v,int w);//向图中添加一条边 v-w 或者 v->w
	public boolean hasEdge(int v,int w);//判断图中两个结点v-w之间是否有边(判断有没有边,不是判断连通性)
	public void show();//打印图的内容
	public Iterable<Integer> adj(int v);//返回图中一个结点V的所有邻边
}

1.3.1.邻接矩阵

图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中的顶点信息,一个二维数组存储图的边的信息;根据邻接矩阵,可以很明确的知道图的一些信息:
1.可以判断两点之间是否有边;
2.要知道某个顶点的度,其实就是这个顶点Vi在邻接矩阵中第i行的元素之和。
3.求顶点Vi的所有邻接点就是将矩阵第i行的元素遍历一遍;
4.若矩阵对称则为有向图;
5.邻接矩阵适用于稠密图;

//邻接矩阵-用于稠密图
public class DensGraph implements Graph {
	private int [][]g;//图的具体数据
	private int m;//边的个数
	private int n;//结点的个数
	private boolean isDir;//判断是否为有向图 true为有向 false为无向
	
	//表示图中有多少个结点 isDir表明此图是否有效
	//n=5 表示结点有5个(0,1,2,3,4)
	public DensGraph() {
		this(0,false);
	}
	public DensGraph(int n,boolean isDir) {
		if(n<0) {
			throw new IllegalArgumentException("图中的节点个数必须大于等于0");
		}
		this.n=n;
		this.m=0;
		this.isDir=isDir;
		this.g=new int[n][n];
	}
	
	@Override
	public int V() {
		return n;
	}

	@Override
	public int E() {
		return 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 ;
		}
		if(isDir) {
			g[v][w]=1;
		}else {
			g[w][v]=1;
			g[v][w]=1;
		}
		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]==1;
	}

	@Override
	public void show() {
		for(int i=0;i<n;i++) {
			for(int j=0;j<n;j++) {
				System.out.print(g[i][j]+" ");
			}
			System.out.println();
		}
	}

	@Override
	public Iterable<Integer> adj(int v) {
		if(v<0||v>=n) {
			throw new IllegalArgumentException(v+"结点不存在");
		}
		Vector<Integer> adjV=new Vector<>();
		for(int i=0;i<n;i++) {
			if(g[v][i]==1) {
				adjV.add(i);
			}
		}
		return adjV;
	}

}

1.3.2.邻接表

由于邻接矩阵在存储变数相对顶点较少的图时会造成空间浪费,所以使用邻接表–数组和链表结合的方式;

//邻接表- 稀疏图
public class SparesGraph implements Graph {
	private int n;//结点个数
	private int m;//边的个数
	private boolean isDir;
	private Vector<Integer> [] g;
	public SparesGraph(int n,boolean isDir) {
		if(n<0) {
			throw new IllegalArgumentException("节点个数必须大于0");
		}
		this.n=n;
		this.m=0;
		this.isDir=isDir;
		g=new Vector[n];
		for(int i=0;i<g.length;i++) {
			g[i]=new Vector<Integer>();
		}
	}
	@Override
	public int V() {
		return n;
	}
	@Override
	public int E() {
		return 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(!isDir) {
			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("ver "+i+" : ");
			for(int j=0;j<g[i].size();j++) {
				System.out.print(g[i].get(j)+" ");
				//System.out.println(g[i].elementAt(j)+" ");
			}
			System.out.println();
		}
		
	}
	@Override
	public Iterable<Integer> adj(int v) {
		if(v<0||v>=n) {
			throw new IllegalArgumentException(v+"结点不存在");
		}
		return g[v];
	}
}

1.4.图的遍历

1.4.1.深度优先遍历(DFS)

深度优先遍历就是一个递归的过程,它从图中的某个顶点V出发,访问此顶点,然后从V的未被访问的邻接点出发深度优先遍历,直至图中所有和V有路劲连同的顶点都被访问到,这里是对于连通图而言;对于非连通图,只需要对他的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次深度优先遍历后,若图中尚有顶点未被访问,则另选图中的一个未被访问的顶点作为起始点,重复上述过程,直至所有顶点都被访问完;

1.4.1.1.邻接矩阵的深度优先
public class ComPath {
	private Graph g; //创建一个图
	private boolean visited[]; //状态数组 true表示已经被访问过
	private int []from; //表示该结点的上一个顶点
	private int s; //起点
	public ComPath(Graph g,int s) {
		this.g=g;
		if(s<0||s>=g.V()) {
			throw new IllegalArgumentException("no this node");
		}
		visited=new boolean[g.V()];
		from=new int[g.V()];
		this.s=s;
		for(int i=0;i<g.V();i++) {
			visited[i]=false;
			from[i]=-1;
		}
		dfs(s);
	}
	private void dfs(int v) {
		visited[v]=true;
		for(int i : g.adj(v)) {
			if(i==1&&!visited[i]) {
				from[i]=v;
				dfs(i);
			}
		}
	}
}

1.4.1.2.邻接表的深度优先
public class SparePath{
	private Graph g;
	private boolean [] visited;
	private int [] from;
	private int s;
	public SparePath(Graph g,int s){
		this.g=g;
		this.s=s;
		visited=new boolean[g.V()];
		from=new int[g.V()];
		for(int i=0;i<g.V();i++){
			from[i]=-1;
			visited[i]=false;
		}
		dfs(s);
	}
	private void dfs(int v){
		visited[v]=true;
		for(int i: g.adj(v)){
			if(!visited[i]){
				from[i]=v;
				dfs(i);
			}
		}
	}
}

1.4.2.广度优先遍历(BFS)

图的广度优先遍历类似于树的层序遍历;

1.4.2.1.邻接矩阵的广度优先遍历
public class DensBFS{
	private Graph g;
	private int s;
	private boolean [] visited;
	private int [] from;
	private int [] order;
	public DensBFS(Graph g,int s){
		this.g=g;
		this.s=s;
		visited=new boolean[g.V()];
		from=new int[g.V()];
		order=new int[g.V()];
		for(int i=0;i<g.V();i++){
			visited[i]=false;
			from[i]=-1;
			order[i]=-1;
		}
		Queue<Integer> queue=new LinkedList<>();
		bfs(s,queue);
	}
	private void bfs(int v,Queue queue){
		queue.add(v);
		visited[v]=true;
		order[v]=0;
		while(!queue.isEmpty()){
			int v=queue.remove();
			for(int i : g.adj(v)){
				if(i==1&&!visited[i]){
					queue.add(i);
					visited[i]=true;
					from[i]=v;
					order[i]=order[v]+1;
				}
			}
		}
	}
}
1.4.2.2.邻接表的广度优先遍历

public class ShortestPath {
	private Graph g;
	private int s;
	private boolean[] visit;
	private int []from;
	private int [] ord;
	private StringBuffer sb;
	public ShortestPath(Graph g,int s) {
		sb=new StringBuffer();
		this.g=g;
		if(s<0||s>=g.V()) {
			throw new IllegalArgumentException("节点不存在");
		}
		visit=new boolean[g.V()];
		from=new int[g.V()];
		ord=new int[g.V()];
		for(int i=0;i<g.V();i++) {
			visit[i]=false;
			from[i]=-1;
			ord[i]=-1;
		}
		this.s=s;
		Queue<Integer>queue=new LinkedList<>();
		queue.add(s);
		visit[s]=true;
		ord[s]=0;
		while(!queue.isEmpty()) {
			int v=queue.remove();
			sb.append(v+"->");
			for(int i:g.adj(v)) {
				if(!visit[i]) {
					queue.add(i);
					visit[i]=true;
					from[i]=v;
					ord[i]=ord[v]+1;
				}
			}
		}
	}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值