无向图-笔记-代码
//API:
public interface Graph { int V();//顶点数 int E();//边数 void addEdge(int v, int w);//添加一条边 Iterable<Integer> adj(int v); //某个顶点v的相邻顶点 String toString(); }
用 邻接表数组 实现的图
public class AdjacencyListArrayGraph implements Graph { int v; int e; IntLinked[] adjList; //2.优化成 key:顶点 val:相邻定点 的 Hash表(如红黑树,或拉链散列表),但为了集中精力在本质的图的特性上,不用过于麻烦的优化方法。 public AdjacencyListArrayGraph(int v) { this.v = v; e = 0; adjList = new IntLinked[v]; for (int i = 0; i < v; i++) { adjList[i] = new IntLinked(); } } @Override public int V() { return v; } @Override public int E() { return e; } @Override public void addEdge(int v, int w) { adjList[v].add(w); adjList[w].add(v); e++; } public String toString() { String res = ""; for (int i = 0; i < v; i++) { res += "[" + i + "] "; Iterable<Integer> it = new ALAGIntIterable(adjList[i].head); Iterator<Integer> itr = it.iterator(); while (itr.hasNext()) { int to = itr.next(); res += to + " "; } res += "\n"; } return res; } @Override public Iterable<Integer> adj(int v) { return new ALAGIntIterable(adjList[v].head); } public class ALAGIntIterable implements Iterable<Integer> { IntNode node; public ALAGIntIterable(IntNode node) { this.node = node; } @Override public Iterator<Integer> iterator() { return new IntNodeIterator(node); } @Override public void forEach(Consumer<? super Integer> action) { } @Override public Spliterator<Integer> spliterator() { return null; } public class IntNodeIterator implements Iterator<Integer> { IntNode node; public IntNodeIterator(IntNode node){ this.node = node; } @Override public boolean hasNext() { return node != null; } @Override public Integer next() { IntNode tmp = node; node = node.n; return tmp.k; } } } public static class ALAGBuilder { File f; public ALAGBuilder(File f) { this.f = f; } public AdjacencyListArrayGraph build() { FileInputStream fis = null; BufferedReader br = null; AdjacencyListArrayGraph g = null; int v = 0, e = 0; try { fis = new FileInputStream(f); br = new BufferedReader(new InputStreamReader(fis)); String txt = null; txt = br.readLine(); if (txt != null) { v = Integer.parseInt(txt); //读取顶点数量 } txt = br.readLine(); if (txt != null) { e = Integer.parseInt(txt); //读取边的数量 ,感到有些多余,直接把剩下的行遍历完就行了 } g = new AdjacencyListArrayGraph(v); int count = 0; while ((txt = br.readLine()) != null) { //遍历剩下的行,加入边到图中,未作有效校验 String[] line = txt.split(" "); int from = Integer.parseInt(line[0]); int to = Integer.parseInt(line[1]); g.addEdge(from, to); } return g; } catch (Exception e1) { e1.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (br != null) { try { br.close(); } catch (IOException e1) { e1.printStackTrace(); } } } return null; } } public static void main(String[] args) { Graph g = new AdjacencyListArrayGraph(10); File f = new File("D:\\aa.txt"); Graph g2 = new ALAGBuilder(f).build(); System.out.println(g2.toString());
System.out.println("degree 0 : " + GraphUtil.degree(g2, 0));
System.out.println("maxDegree : " + GraphUtil.maxDegree(g2));
System.out.println("avgDegree : " + GraphUtil.avgDegree(g2));
System.out.println("numberOfSelfloops : " + GraphUtil.numberOfSelfloops(g2));
//深度搜索
Search search = new DeepFirstSearch(g2,2);
System.out.println("\n=== Search");
System.out.println("2 count : " + search.count());
System.out.println("2 marked 3 : " + search.marked(3)); //2个顶点是否连通? = 2个顶点之间是否有至少一条路径存在?
System.out.println("2 marked 1 : " + search.marked(1));
//图中有多少个连通子图?
//1.将第通过深度搜索出来的连通子图中的顶点,从图中删除,剩下去除掉第一个连通子图的剩余的子图 (suck)
//2.从剩余的顶点中随机找一个顶点,再一次做深度搜索
//3.重复步骤1,直到图中没有剩余的顶点为之,循环的次数就是整个图中连通子图的数量
//TODO
}
aa.txt 的文件格式:
顶点数量
边数量
顶点1 顶点2
。。。
。。。
顶点x 顶点y
头2行是一行一个单独的数字
后面是记录 边的信息的,一行一条边
aa.txt
5 8 0 1 0 2 1 3 1 4 2 3 2 4 0 0 1 1
一些工具类
public class GraphUtil { //度 public static int degree(Graph g, int v) { Iterable<Integer> it = g.adj(v); int count = 0; for (int n : it) { count++; } return count; } //所有定点最大度 public static int maxDegree(Graph g) { int max = 0; for (int v = 0; v < g.V(); v++) { int d = degree(g, v); max = max < d ? d : max; } return max; } //所有定点平均度数 public static double avgDegree(Graph g) { return 2.0 * g.E() / g.V(); // * 2 因为 一条边 被算了2次(出现在一对定点的各自的相邻顶点列表) } //图中自环的个数 public static int numberOfSelfloops(Graph g) { int count = 0; for (int v = 0; v < g.V(); v++) for (int w : g.adj(v)) if (v == w) count++; return count / 2; //因为addEdge 的实现,一个自环会被加2次到同一个定点上,所以除以2 } }
广度优先:
public class DeepFirstSearch implements Search { int count; boolean[] marked; //可达的顶点=true public DeepFirstSearch(Graph g, int s) { marked = new boolean[g.V()]; dfs(g, s); } private void dfs(Graph g, int v) { marked[v] = true; count++; for (int w : g.adj(v)) { if (!marked[w]) dfs(g, w);//递归,有栈的作用 } } @Override public boolean marked(int v) { return marked[v]; } @Override public int count() { //从 s点开始,连通的点的个数(不算自环) return count; } }
小而强劲
结果:
[0] 1 2 0 0
[1] 0 3 4 1 1
[2] 0 3 4
[3] 1 2
[4] 1 2
degree 0 : 4
maxDegree : 5
avgDegree : 3.2
numberOfSelfloops : 2
=== Search
2 count : 5
2 marked 3 : true
2 marked 1 : true
图是一种数据结构,一些内容来自数学的图论研究成果。
从
1.有/无向 (方向,单向)
2。有/无权(权重)
可组合出4种图,无向图,有向图,有权图,有向有权图
这里的例子是无向图
常见概念有:
顶点,边,度,自环,连通图,平行边,稠密/稀疏图,子图,树,森林,无环图,二分图,连通子图,生成树森林等(挺多的)
连通图:是一个整体,图中任意一点可以到达这个图上其他任意点
生成树:只是连通图的一副子图
生成树森林:一个图的所有连通子图的生成树集合
深度搜索:
遍历点的相邻点时,用栈作为临时存储结构(可以用递归实现)(优先处理起始点的第一个相邻点,然后再从该第一个相邻点作为新起始点)
广度搜索:
用队列(优先把起始点周围的一圈相邻点处理完)
无向图, 环检测 ,假设不存在自环或平行边
public class CycGraphTest { //无向图, 环检测 ,假设不存在自环或平行边 Graph g; boolean[] marked; //是否已访问过 boolean hasCyc; // int[] fromVertex; public CycGraphTest(Graph g) { this.g = g; marked = new boolean[g.v()]; // fromVertex = new int[g.v()]; // Arrays.fill(fromVertex, -1); hasCyc = false; for (int s = 0; s < g.v(); s++) { if (!marked[s]) { dfs(s, s); } } } private void dfs(int v, int fromV) { marked[v] = true; // fromVertex[v] = fromV; for (Integer w : g.adj(v)) { if (!marked[w]) { dfs(w, v); } else if (fromV != w) { //不是来的点(无向图), // && fromVertex[w] != v 访问者也不是我(说明被从其他路径访问过) hasCyc = true; } } } public boolean isHasCyc() { return hasCyc; } public static void main(String[] args) { Graph g = new Graph(6); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(2, 0); g.addEdge(3, 4); System.out.println("Vertex : edgeTo"); System.out.println(g.toString()); CycGraphTest cc = new CycGraphTest(g); System.out.println("isHasCyc: " + cc.isHasCyc()); } }
个别大小写需要改一下。。。
输出
Vertex : edgeTo 0 : 2 1 1 : 2 0 2 : 0 1 3 : 4 4 : 3 5 : isHasCyc: true
是否是二分图 (顶点只由2个颜色标记,且相邻的顶点必须不同色)
public class ColorGraphTest { //是否是二分图 (顶点只由2个颜色标记,且相邻的顶点必须不同色) boolean[] colors; boolean[] marked; boolean isBinGraph; Graph g; public ColorGraphTest(Graph g) { this.g = g; colors = new boolean[g.v()]; marked = new boolean[g.v()]; isBinGraph = true; for (int s = 0; s < g.v(); s++) { if(!marked[s]) //跑完所有子图 dfs(s, s); } } public void dfs(int v, int fromV) { marked[v] = true; colors[v] = !colors[fromV]; for (int w : g.adj(v)) { if (!marked[w]) { dfs(w, v); } else if (colors[v] == colors[w]) { //存在相邻的2个已访问过的点 的颜色 一致,说明不是二分图 isBinGraph = false; System.out.println("v-w " + v + "-" + w + " colors v-w " + colors[v] + "-" + colors[w]); } } } public boolean isBinGraph() { return isBinGraph; } public static void main(String[] args) { Graph g = new Graph(6); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(2, 0); g.addEdge(3, 4); System.out.println("Vertex : edgeTo"); System.out.println(g.toString()); ColorGraphTest ct = new ColorGraphTest(g); System.out.println("isBinGraph: " + ct.isBinGraph()); } }
Vertex : edgeTo 0 : 2 1 1 : 2 0 2 : 0 1 3 : 4 4 : 3 5 : v-w 1-0 colors v-w true-true v-w 0-1 colors v-w true-true isBinGraph: false
连通图, 连通子图判断
public class CCTest { //连通图, 连通子图判断 Graph g; boolean[] marked; //是否已访问过 int[] id; //每个顶点对应的连通图id int count; //连通图个数 public CCTest(Graph g) { this.g = g; marked = new boolean[g.v()]; id = new int[g.v()]; count = 0; for (int s = 0; s < g.v(); s++) { if (!marked[s]) { dfs(s); count++; } } } private void dfs(int v) { marked[v] = true; id[v] = count; for (Integer w : g.adj(v)) { if (!marked[w]) { dfs(w); } } } public int unionId(int v) { return id[v]; } public int count() { return count; } public static void main(String[] args) { Graph g = new Graph(5); g.addEdge(0, 1); g.addEdge(1, 2); System.out.println("Vertex : edgeTo"); System.out.println(g.toString()); CCTest cc = new CCTest(g); System.out.println("unionCount : " + cc.count()); System.out.println("Vertex : unionId"); for (int v = 0; v < g.v(); v++) System.out.println(v + " : " + cc.unionId(v)); } }
Vertex : edgeTo 0 : 1 1 : 2 0 2 : 1 3 : 4 : unionCount : 3 Vertex : unionId 0 : 0 1 : 0 2 : 0 3 : 1 4 : 2
广度优先,记录路径
public class BFSPathTest { //广度优先,记录路径 Graph g; boolean[] marked; int[] fromVertex; int s; public BFSPathTest(Graph g, int s) { //s 起点 this.g = g; this.s = s; marked = new boolean[g.v()]; fromVertex = new int[g.v()]; Arrays.fill(fromVertex, -1); bfs(s); } //广度优先 private void bfs(int s) { Queue<Integer> queue = new LinkedList(); queue.add(s); marked[s] = true; fromVertex[s] = s; Integer n; while ((n = queue.poll()) != null) { System.out.println("v " + n); for (int w : g.adj(n)) { if (!marked[w]) { marked[w] = true; fromVertex[w] = n; System.out.println("add +" + w); queue.add(w); } } } } public boolean hasPathTo(int v) { return marked[v]; //fromVertex[v] != -1; 不用这个 因为不考虑其他连通子图,只考虑和s相关的连通图 } public List<Integer> pathTo(int v) { List l = new ArrayList(); if(!hasPathTo(v)) return l; Stack stack = new Stack(); stack.push(v); while (v != s) { stack.push(fromVertex[v]); v = fromVertex[v]; } // stack.push(s); while (!stack.empty()) { l.add(stack.pop()); } return l; } public static void main(String[] args) { Graph g = new Graph(7); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(2, 0); g.addEdge(3, 0); g.addEdge(4, 5); System.out.println("Vertex : edgeTo"); System.out.println(g.toString()); BFSPathTest pg = new BFSPathTest(g, 1); for (int v = 0; v < g.v(); v++) { System.out.println("hasPathTo from 1 to " + v + " :" + pg.hasPathTo(v)); System.out.println(" pathTo " + pg.pathTo(v)); } } }
Vertex : edgeTo 0 : 3 2 1 1 : 2 0 2 : 0 1 3 : 0 4 : 5 5 : 4 6 : v 1 add +2 add +0 v 2 v 0 add +3 v 3 hasPathTo from 1 to 0 :true pathTo [1, 0] hasPathTo from 1 to 1 :true pathTo [1] hasPathTo from 1 to 2 :true pathTo [1, 2] hasPathTo from 1 to 3 :true pathTo [1, 0, 3] hasPathTo from 1 to 4 :false pathTo [] hasPathTo from 1 to 5 :false pathTo [] hasPathTo from 1 to 6 :false pathTo []
在一个有V个顶点的图中,最多有几条边?
(V*(V-1))/2 条
V,每个顶点,与图中除了自己以外的顶点相连 有 V-1 条,每个这样的顶点都如此相连=V*(V-1), 去掉每条边重复了2次 ,除以2 =(V*(V-1)) / 2
在一个有V个顶点的连通图中,最少有几条边?
V-1 条
最后抱怨一下(你要接受我的精神污染,你生存的意义在于被别人压榨):
感觉自己已经失去独立思考,自主自考,自我意识了。 思考能力。。。。
基本都是 copy别人的东西,自己也不太动脑子了,疯狂看别人的想法,实现,思想, 自己也不加联想和批判。 一股脑的 你说有什么优势 ,那我就 ‘嗯!’ 有什么优势,记也记不住什么东西。如同机械般的‘思考’ 和 ‘记忆’