c2java 第6篇 图的表示,最短路,圈检测和拓扑排序
2014-04-21 19:54
369 查看
/*
图的表示
===
最简单的是临接矩阵了
int n = 10;
int[][] adjMat = new int
;
如果边数接近顶点数的平方,就用这个很合适。
如果边比较稀疏,顶点又比较多,为了不浪费空间,可选用临接表
int i, n = 10;
int[][] adjList = new int
[0];
for(i = 0; i < adjList.length; ++i){
adjList[i] = new int[i+1];
}
这种表示有个优点是查询一个顶点的所有临接顶点很高效,并且和C比较,
我们不需要单独记录每个数组的长度。
上面两种写法是为了便于移植到C。如果图顶点或边发生变化,纵向和横向的组合可以用:
Vector, ArrayList, LinkedList. 最动态的是LinkedList+LinkedList.
如果图需要动态的增加删除顶点和边,则用链接表比较合适。
通常情况下,比如oj,图建好了之后不会变化,因此下面的代码采用第二种写法。
但是如果给的是点对,需要两遍扫描,对于stdin是不行的。另外,表采用数组还是链接引用的形式,
表面上看,构造图时数组来回复制效率低下,但是以后的访问因为cache局部性变得高效。
属性表示
====
顶点的附加属性,可以开数组int attr
表示;
边的附加属性比如权重,必须Edge
了。对于一些需要直接attr(u,v)的,attr
更简单。
图的遍历
====
dfs可用来判断一个图是不是有向无环图,做拓扑排序。
传统教科书上拓扑排序一般是从入度为零的顶点开始的,基于dfs则不必要。
问题1:拓扑排序结果不唯一,对于oj来说,如何判定一个结果是对的?
回答:检查图的每条边是否和这个结果是否相容。
问题2:第一次测试时因为没有加颜色判断,导致jvm栈不够。jvm 默认的栈空间有多大?
回答:-Xss默认每个线程栈是1M [1]
问题3:手动模拟栈与递归的写法,谁好谁坏?
bfs 可用来找最短路,这里假定直接相邻的两个顶点的距离是1。具体做法:
在把相邻顶点入队列前,记录他的父顶点。
关于队列,我们容知道最大为顶点个数n, 但是基于数组必然要循环利用空间,手写麻烦,于是采用标准库的LinkedList。
关于距离1的假设,在一个1日常活动的区域内,比如坐公交,似乎是合理的。这里隐含的是假设一个区域内是均衡的。
Ref
[1] http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html http://stackoverflow.com/questions/17090778/optimization-on-algorithm-to-check-whether-for-a-given-directed-graph-there-is
*/
import java.io.*;
import java.util.*;
public class Graph
{
int n, m; /*顶点数,边数*/
int[][] adjList;
int[] edgeCnt, edgeCap;
byte[] color; /*for dfs: 0--white, 1--gray, 2--black*/
int initGraph(int vertexNum, int edgeNum)
{
int i;
n = vertexNum; m = edgeNum;
adjList = new int
[0];
edgeCnt = new int
;
edgeCap = new int
;
for(i = 0; i < n; ++i){
edgeCnt[i] = 0;
edgeCap[i] = 10;
adjList[i] = new int[edgeCap[i]];
}
color = new byte
;
return 0;
}
void trimToSize()
{
int i;
for(i = 0; i < n; ++i){
adjList[i] = Arrays.copyOf(adjList[i], edgeCnt[i]);
}
edgeCnt = null; edgeCap = null;
}
int addEdge(int v, int w)
{
if(edgeCnt[v] >= edgeCap[v]){
int newCap = edgeCap[v] + 10;
adjList[v] = Arrays.copyOf(adjList[v], newCap);
edgeCap[v] = newCap;
}
adjList[v][edgeCnt[v]++] = w;
return 0;
}
int[] getNeighbors(int v)
{
return adjList[v];
}
int buildGraph() throws IOException
{/*return 0 if ok*/
/*假设输入顶点数,边数,之后是m对边(v,w)*/
int i, n, m, v, w;
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
if((n < 1) || (m < 1)){return -1;}
initGraph(n, m);
for(i = 0; i < m; ++i){
v = in.nextInt(); // - 1;
w = in.nextInt(); // - 1;
addEdge(v, w);
}
trimToSize();
return 0;
}
void dfs(int v, StringBuffer out)
{
int i, w = 0;
int[] nei = getNeighbors(v);
color[v] = 1;
for(i = 0; i < nei.length; ++i){
w = nei[i];
if(0 == color[w])dfs(w, out);
else if(1 == color[w]){/*a cycle is found.*/}
}
color[w] = 2;
out.insert(0, ',');
out.insert(0, v);
//System.out.printf("v = %d out = %s %n", v, out);
}
void topoSort(StringBuffer out )
{
int i;
out.delete(0, out.length());
Arrays.fill(color, (byte)0);
for(i = 0; i < n; ++i){
if(0 == color[i])dfs(i, out);
}
}
void bfs(int v, StringBuffer out)
{
int i, w;
int[] nei;
LinkedList<Integer> qu = new LinkedList<Integer>();
out.delete(0, out.length());
Arrays.fill(color, (byte)0);
qu.push(v);
while(!qu.isEmpty()){
v = qu.pop();
out.append(v);
out.append(',');
color[v] = 1; /*visited*/
nei = getNeighbors(v);
for(i = 0; i < nei.length; ++i){
w = nei[i];
if(0 == color[w])qu.push(w);
}
}
}
public static void main (String[] arg) throws IOException
{
StringBuffer out = new StringBuffer("");
Graph g = new Graph();
g.buildGraph();
g.dfs(0, out);
System.out.println("dfs: " + out);
g.bfs(0, out);
System.out.println("bfs: " + out);
g.topoSort(out);
System.out.println("topoSort: " + out);
}
}
/*
$ cat in.txt
9 9
0 1 0 3
1 2
2 3
5 6 5 8
6 3 6 7
8 7
6 7
0 1 0 2
1 3
3 4
4 1 4 5
5 2
ludi@ubun:~/java
$ javac -g Graph.java && cat in.txt | java Graph
dfs: 0,1,2,3,
bfs: 0,3,1,2,
topoSort: 5,8,6,7,4,0,1,2,3,
ludi@ubun:~/java
*/
图的表示
===
最简单的是临接矩阵了
int n = 10;
int[][] adjMat = new int
;
如果边数接近顶点数的平方,就用这个很合适。
如果边比较稀疏,顶点又比较多,为了不浪费空间,可选用临接表
int i, n = 10;
int[][] adjList = new int
[0];
for(i = 0; i < adjList.length; ++i){
adjList[i] = new int[i+1];
}
这种表示有个优点是查询一个顶点的所有临接顶点很高效,并且和C比较,
我们不需要单独记录每个数组的长度。
上面两种写法是为了便于移植到C。如果图顶点或边发生变化,纵向和横向的组合可以用:
Vector, ArrayList, LinkedList. 最动态的是LinkedList+LinkedList.
如果图需要动态的增加删除顶点和边,则用链接表比较合适。
通常情况下,比如oj,图建好了之后不会变化,因此下面的代码采用第二种写法。
但是如果给的是点对,需要两遍扫描,对于stdin是不行的。另外,表采用数组还是链接引用的形式,
表面上看,构造图时数组来回复制效率低下,但是以后的访问因为cache局部性变得高效。
属性表示
====
顶点的附加属性,可以开数组int attr
表示;
边的附加属性比如权重,必须Edge
了。对于一些需要直接attr(u,v)的,attr
更简单。
图的遍历
====
dfs可用来判断一个图是不是有向无环图,做拓扑排序。
传统教科书上拓扑排序一般是从入度为零的顶点开始的,基于dfs则不必要。
问题1:拓扑排序结果不唯一,对于oj来说,如何判定一个结果是对的?
回答:检查图的每条边是否和这个结果是否相容。
问题2:第一次测试时因为没有加颜色判断,导致jvm栈不够。jvm 默认的栈空间有多大?
回答:-Xss默认每个线程栈是1M [1]
问题3:手动模拟栈与递归的写法,谁好谁坏?
bfs 可用来找最短路,这里假定直接相邻的两个顶点的距离是1。具体做法:
在把相邻顶点入队列前,记录他的父顶点。
关于队列,我们容知道最大为顶点个数n, 但是基于数组必然要循环利用空间,手写麻烦,于是采用标准库的LinkedList。
关于距离1的假设,在一个1日常活动的区域内,比如坐公交,似乎是合理的。这里隐含的是假设一个区域内是均衡的。
Ref
[1] http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html http://stackoverflow.com/questions/17090778/optimization-on-algorithm-to-check-whether-for-a-given-directed-graph-there-is
*/
import java.io.*;
import java.util.*;
public class Graph
{
int n, m; /*顶点数,边数*/
int[][] adjList;
int[] edgeCnt, edgeCap;
byte[] color; /*for dfs: 0--white, 1--gray, 2--black*/
int initGraph(int vertexNum, int edgeNum)
{
int i;
n = vertexNum; m = edgeNum;
adjList = new int
[0];
edgeCnt = new int
;
edgeCap = new int
;
for(i = 0; i < n; ++i){
edgeCnt[i] = 0;
edgeCap[i] = 10;
adjList[i] = new int[edgeCap[i]];
}
color = new byte
;
return 0;
}
void trimToSize()
{
int i;
for(i = 0; i < n; ++i){
adjList[i] = Arrays.copyOf(adjList[i], edgeCnt[i]);
}
edgeCnt = null; edgeCap = null;
}
int addEdge(int v, int w)
{
if(edgeCnt[v] >= edgeCap[v]){
int newCap = edgeCap[v] + 10;
adjList[v] = Arrays.copyOf(adjList[v], newCap);
edgeCap[v] = newCap;
}
adjList[v][edgeCnt[v]++] = w;
return 0;
}
int[] getNeighbors(int v)
{
return adjList[v];
}
int buildGraph() throws IOException
{/*return 0 if ok*/
/*假设输入顶点数,边数,之后是m对边(v,w)*/
int i, n, m, v, w;
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
if((n < 1) || (m < 1)){return -1;}
initGraph(n, m);
for(i = 0; i < m; ++i){
v = in.nextInt(); // - 1;
w = in.nextInt(); // - 1;
addEdge(v, w);
}
trimToSize();
return 0;
}
void dfs(int v, StringBuffer out)
{
int i, w = 0;
int[] nei = getNeighbors(v);
color[v] = 1;
for(i = 0; i < nei.length; ++i){
w = nei[i];
if(0 == color[w])dfs(w, out);
else if(1 == color[w]){/*a cycle is found.*/}
}
color[w] = 2;
out.insert(0, ',');
out.insert(0, v);
//System.out.printf("v = %d out = %s %n", v, out);
}
void topoSort(StringBuffer out )
{
int i;
out.delete(0, out.length());
Arrays.fill(color, (byte)0);
for(i = 0; i < n; ++i){
if(0 == color[i])dfs(i, out);
}
}
void bfs(int v, StringBuffer out)
{
int i, w;
int[] nei;
LinkedList<Integer> qu = new LinkedList<Integer>();
out.delete(0, out.length());
Arrays.fill(color, (byte)0);
qu.push(v);
while(!qu.isEmpty()){
v = qu.pop();
out.append(v);
out.append(',');
color[v] = 1; /*visited*/
nei = getNeighbors(v);
for(i = 0; i < nei.length; ++i){
w = nei[i];
if(0 == color[w])qu.push(w);
}
}
}
public static void main (String[] arg) throws IOException
{
StringBuffer out = new StringBuffer("");
Graph g = new Graph();
g.buildGraph();
g.dfs(0, out);
System.out.println("dfs: " + out);
g.bfs(0, out);
System.out.println("bfs: " + out);
g.topoSort(out);
System.out.println("topoSort: " + out);
}
}
/*
$ cat in.txt
9 9
0 1 0 3
1 2
2 3
5 6 5 8
6 3 6 7
8 7
6 7
0 1 0 2
1 3
3 4
4 1 4 5
5 2
ludi@ubun:~/java
$ javac -g Graph.java && cat in.txt | java Graph
dfs: 0,1,2,3,
bfs: 0,3,1,2,
topoSort: 5,8,6,7,4,0,1,2,3,
ludi@ubun:~/java
*/
相关文章推荐
- Java中短路
- Java设计一个表示学生的类
- 如何检测Java 字符串中是否包含某字符
- java多线程表示10个人抢占2个位置
- 蓝桥杯 算法训练 2的次幂表示 java
- java检测http请求的ip地址 Java问题通用解决代码
- java短路
- VisualVM 检测不到本地JAVA程序
- 算法-蓝桥杯-算法训练 最短路(JAVA)
- Java基础类型与其二进制表示
- 2017.4.3 机房测试 (并查集,最短路,拓扑排序,最小生成树)
- HTTP协议----(2)Java程序检测HTTP服务是否打开
- 蓝桥杯 算法训练 2的次幂表示(Java解题)
- Java学习笔记——文件存在检测
- java实现单项链表以及如何检测回环
- java中类型所占字节数以及可表示的数字个数
- 今天开始学Java log2N的表示
- Java日期表示
- 剑指Offer(Java版):把十进制数字用A~Z表示成二十六进制。
- Java检测文件编码