有向图
import java.io.File;
import java.io.IOException;
import java.util.TreeSet;
import java.util.Scanner;
/// 暂时只支持无向无权图
public class Graph {
private int V;
private int E;
private TreeSet<Integer>[] adj;
public Graph(String filename){
File file = new File(filename);
try(Scanner scanner = new Scanner(file)){
V = scanner.nextInt();
if(V < 0) throw new IllegalArgumentException("V must be non-negative");
adj = new TreeSet[V];
for(int i = 0; i < V; i ++)
adj[i] = new TreeSet<Integer>();
E = scanner.nextInt();
if(E < 0) throw new IllegalArgumentException("E must be non-negative");
for(int i = 0; i < E; i ++){
int a = scanner.nextInt();
validateVertex(a);
int b = scanner.nextInt();
validateVertex(b);
if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");
if(adj[a].contains(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");
adj[a].add(b);
adj[b].add(a);
}
}
catch(IOException e){
e.printStackTrace();
}
}
public void validateVertex(int v){
if(v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + "is invalid");
}
public int V(){
return V;
}
public int E(){
return E;
}
public boolean hasEdge(int v, int w){
validateVertex(v);
validateVertex(w);
return adj[v].contains(w);
}
public Iterable<Integer> adj(int v){
validateVertex(v);
return adj[v];
}
public int degree(int v){
validateVertex(v);
return adj[v].size();
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(String.format("V = %d, E = %d\n", V, E));
for(int v = 0; v < V; v ++){
sb.append(String.format("%d : ", v));
for(int w : adj[v])
sb.append(String.format("%d ", w));
sb.append('\n');
}
return sb.toString();
}
public static void main(String[] args){
Graph g = new Graph("g.txt");
System.out.print(g);
}
}
有权图修改方式同上
package com.graph.DirectedGraph.DirectedGraph.src;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import java.util.Scanner;
/// 带权图(支持有向,无向)
public class WeightedGraph{
private int V;
private int E;
private TreeMap<Integer, Integer>[] adj;
private boolean directed;
public WeightedGraph(String filename, boolean directed){
this.directed = directed;
File file = new File(filename);
try(Scanner scanner = new Scanner(file)){
V = scanner.nextInt();
if(V < 0) throw new IllegalArgumentException("V must be non-negative");
adj = new TreeMap[V];
for(int i = 0; i < V; i ++)
adj[i] = new TreeMap<Integer, Integer>();
E = scanner.nextInt();
if(E < 0) throw new IllegalArgumentException("E must be non-negative");
for(int i = 0; i < E; i ++){
int a = scanner.nextInt();
validateVertex(a);
int b = scanner.nextInt();
validateVertex(b);
int v = scanner.nextInt();
if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");
if(adj[a].containsKey(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");
adj[a].put(b, v);
if(!directed)
adj[b].put(a, v);
}
}
catch(IOException e){
e.printStackTrace();
}
}
public WeightedGraph(String filename){
this(filename, false);
}
public boolean isDirected(){
return directed;
}
public void validateVertex(int v){
if(v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + "is invalid");
}
public int V(){
return V;
}
public int E(){
return E;
}
public boolean hasEdge(int v, int w){
validateVertex(v);
validateVertex(w);
return adj[v].containsKey(w);
}
public int getWeight(int v, int w){
if(hasEdge(v, w)) return adj[v].get(w);
throw new IllegalArgumentException(String.format("No edge %d-%d", v, w));
}
public Iterable<Integer> adj(int v){
validateVertex(v);
return adj[v].keySet();
}
// public int degree(int v){
// validateVertex(v);
// return adj[v].size();
// }
public void removeEdge(int v, int w){
validateVertex(v);
validateVertex(w);
if(adj[v].containsKey(w)) E --;
adj[v].remove(w);
if(!directed)
adj[w].remove(v);
}
@Override
public Object clone(){
try{
WeightedGraph cloned = (WeightedGraph) super.clone();
cloned.adj = new TreeMap[V];
for(int v = 0; v < V; v ++){
cloned.adj[v] = new TreeMap<Integer, Integer>();
for(Map.Entry<Integer, Integer> entry: adj[v].entrySet())
cloned.adj[v].put(entry.getKey(), entry.getValue());
}
return cloned;
}
catch (CloneNotSupportedException e){
e.printStackTrace();
}
return null;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(String.format("V = %d, E = %d, directed = %b\n", V, E, directed));
for(int v = 0; v < V; v ++){
sb.append(String.format("%d : ", v));
for(Map.Entry<Integer, Integer> entry: adj[v].entrySet())
sb.append(String.format("(%d: %d) ", entry.getKey(), entry.getValue()));
sb.append('\n');
}
return sb.toString();
}
public static void main(String[] args){
WeightedGraph wg = new WeightedGraph("wg.txt", true);
System.out.print(wg);
}
}
几乎完全一样
package com.graph.DirectedGraph.DirectedGraphAlgorithms.src;
import java.util.ArrayList;
public class GraphDFS {
private Graph G;
private boolean[] visited;
private ArrayList<Integer> pre = new ArrayList<>();
private ArrayList<Integer> post = new ArrayList<>();
public GraphDFS(Graph G){
this.G = G;
visited = new boolean[G.V()];
for(int v = 0; v < G.V(); v ++)
if(!visited[v])
dfs(v);
}
private void dfs(int v){
visited[v] = true;
pre.add(v);
for(int w: G.adj(v))
if(!visited[w])
dfs(w);
post.add(v);
}
public Iterable<Integer> pre(){
return pre;
}
public Iterable<Integer> post(){
return post;
}
public static void main(String[] args){
Graph g = new Graph("ug.txt", true);//改了是否是有向图
GraphDFS graphDFS = new GraphDFS(g);
System.out.println("DFS preOrder : " + graphDFS.pre());
System.out.println("DFS postOrder : " + graphDFS.post());
}
}
package com.graph.DirectedGraph.DirectedGraphAlgorithms.src;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class GraphBFS {
private Graph G;
private boolean[] visited;
private ArrayList<Integer> order = new ArrayList<>();
public GraphBFS(Graph G){
this.G = G;
visited = new boolean[G.V()];
for(int v = 0; v < G.V(); v ++)
if(!visited[v])
bfs(v);
}
private void bfs(int s){
Queue<Integer> queue = new LinkedList<>();
queue.add(s);
visited[s] = true;
while(!queue.isEmpty()){
int v = queue.remove();
order.add(v);
for(int w: G.adj(v))
if(!visited[w]){
queue.add(w);
visited[w] = true;
}
}
}
public Iterable<Integer> order(){
return order;
}
public static void main(String[] args){
Graph g = new Graph("ug.txt", true);
GraphBFS graphBFS = new GraphBFS(g);
System.out.println("BFS Order : " + graphBFS.order());
}
}
也一样
Floyed算法
package com.graph.DirectedGraph.DirectedGraphAlgorithms.src;
import java.util.Arrays;
public class Floyed {
private WeightedGraph G;
private int[][] dis;
private boolean hasNegCycle = false;
public Floyed(WeightedGraph G){
this.G = G;
dis = new int[G.V()][G.V()];
for(int v = 0; v < G.V(); v ++)
Arrays.fill(dis[v], Integer.MAX_VALUE);
for(int v = 0; v < G.V(); v ++){
dis[v][v] = 0;
for(int w: G.adj(v))
dis[v][w] = G.getWeight(v, w);
}
for(int t = 0; t < G.V(); t ++)
for(int v = 0; v < G.V(); v ++)
for(int w = 0; w < G.V(); w ++)
if(dis[v][t] != Integer.MAX_VALUE && dis[t][w] != Integer.MAX_VALUE
&& dis[v][t] + dis[t][w] < dis[v][w])
dis[v][w] = dis[v][t] + dis[t][w];
for(int v = 0; v < G.V(); v ++)
if(dis[v][v] < 0)
hasNegCycle = true;
}
public boolean hasNegativeCycle(){
return hasNegCycle;
}
public boolean isConnectedTo(int v, int w){
G.validateVertex(v);
G.validateVertex(w);
return dis[v][w] != Integer.MAX_VALUE;
}
public int distTo(int v, int w){
G.validateVertex(v);
G.validateVertex(w);
return dis[v][w];
}
static public void main(String[] args){
WeightedGraph g = new WeightedGraph("wg.txt", true);
Floyed floyed = new Floyed(g);
if(!floyed.hasNegativeCycle()){
for(int v = 0; v < g.V(); v ++){
for(int w = 0; w < g.V(); w ++)
System.out.print(floyed.distTo(v, w) + " ");
System.out.println();
}
}
else
System.out.println("exist negative cycle.");
WeightedGraph g2 = new WeightedGraph("wg2.txt", true);
Floyed floyed2 = new Floyed(g2);
if(!floyed2.hasNegativeCycle()){
for(int v = 0; v < g.V(); v ++){
for(int w = 0; w < g.V(); w ++)
System.out.print(floyed2.distTo(v, w) + " ");
System.out.println();
}
}
else
System.out.println("exist negative cycle.");
}
}
Bellman-Ford算法
package com.graph.DirectedGraph.DirectedGraphAlgorithms.src;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class BellmanFord {
private WeightedGraph G;
private int s;
private int[] dis;
private int[] pre;
private boolean hasNegCycle = false;
public BellmanFord(WeightedGraph G, int s){
this.G = G;
G.validateVertex(s);
this.s = s;
dis = new int[G.V()];
Arrays.fill(dis, Integer.MAX_VALUE);
dis[s] = 0;
pre = new int[G.V()];
Arrays.fill(pre, -1);
for(int pass = 1; pass < G.V(); pass ++){
for(int v = 0; v < G.V(); v ++)
for(int w: G.adj(v))
if(dis[v] != Integer.MAX_VALUE &&
dis[v] + G.getWeight(v, w) < dis[w]){
dis[w] = dis[v] + G.getWeight(v, w);
pre[w] = v;
}
}
for(int v = 0; v < G.V(); v ++)
for(int w : G.adj(v))
if(dis[v] != Integer.MAX_VALUE &&
dis[v] + G.getWeight(v, w) < dis[w])
hasNegCycle = true;
}
public boolean hasNegativeCycle(){
return hasNegCycle;
}
public boolean isConnectedTo(int v){
G.validateVertex(v);
return dis[v] != Integer.MAX_VALUE;
}
public int distTo(int v){
G.validateVertex(v);
if(hasNegCycle) throw new RuntimeException("exist negative cycle.");
return dis[v];
}
public Iterable<Integer> path(int t){
ArrayList<Integer> res = new ArrayList<Integer>();
if(!isConnectedTo(t)) return res;
int cur = t;
while(cur != s){
res.add(cur);
cur = pre[cur];
}
res.add(s);
Collections.reverse(res);
return res;
}
static public void main(String[] args){
WeightedGraph g = new WeightedGraph("wg.txt", true);
BellmanFord bf = new BellmanFord(g, 0);
if(!bf.hasNegativeCycle()){
for(int v = 0; v < g.V(); v ++)
System.out.print(bf.distTo(v) + " ");
System.out.println();
System.out.println(bf.path(1));
}
else
System.out.println("exist negative cycle.");
WeightedGraph g2 = new WeightedGraph("wg2.txt", true);
BellmanFord bf2 = new BellmanFord(g2, 0);
if(!bf2.hasNegativeCycle()){
for(int v = 0; v < g2.V(); v ++)
System.out.print(bf2.distTo(v) + " ");
System.out.println();
}
else
System.out.println("exist negative cycle.");
}
}
对有负权环的问题,仍然无法正确运行
有向图的环检测
退回时,取消当前路径标值
如果这样就表示有环
package com.graph.DirectedGraph.DirectedCycleDetection.src;
public class DirectedCycleDetection {
private Graph G;
private boolean[] visited;
private boolean[] onPath;
private boolean hasCycle = false;
public DirectedCycleDetection(Graph G){
if(!G.isDirected())//判断有向性,不能检测无向图
throw new IllegalArgumentException("DirectedCycleDetection only works in directed graph.");
this.G = G;
visited = new boolean[G.V()];
onPath = new boolean[G.V()];//给路径数组开空间
for(int v = 0; v < G.V(); v ++)
if(!visited[v])
if(dfs(v)){
hasCycle = true;
break;
}
}
// 从顶点 v 开始,判断图中是否有环
private boolean dfs(int v){
visited[v] = true;
onPath[v] = true;//标记路径
for(int w: G.adj(v))
if(!visited[w]){
if(dfs(w)) return true;
}
else if(onPath[w])//对有向图来说,不需要判别是否是父亲节点,只需要知道是否在路径上
return true;
onPath[v] = false;//退出路径时,抹掉路径
return false;
}
public boolean hasCycle(){
return hasCycle;
}
public static void main(String[] args){
Graph g = new Graph("ug.txt", true);
DirectedCycleDetection cycleDetection = new DirectedCycleDetection(g);
System.out.println(cycleDetection.hasCycle());
Graph g2 = new Graph("ug2.txt", true);
DirectedCycleDetection cycleDetection2 = new DirectedCycleDetection(g2);
System.out.println(cycleDetection2.hasCycle());
}
}
有向图的度
package com.graph.DirectedGraph.DirectedGraphDegrees.src;
import java.io.File;
import java.io.IOException;
import java.util.TreeSet;
import java.util.Scanner;
/// 无权图(支持有向,无向)
public class Graph implements Cloneable{
private int V;
private int E;
private TreeSet<Integer>[] adj;
private boolean directed;
private int[] indegrees, outdegrees;//创建人度和出度的成员变量
public Graph(String filename, boolean directed){
this.directed = directed;
File file = new File(filename);
try(Scanner scanner = new Scanner(file)){
V = scanner.nextInt();
if(V < 0) throw new IllegalArgumentException("V must be non-negative");
adj = new TreeSet[V];
for(int i = 0; i < V; i ++)
adj[i] = new TreeSet<Integer>();
indegrees = new int[V];
outdegrees = new int[V];//开空间
E = scanner.nextInt();
if(E < 0) throw new IllegalArgumentException("E must be non-negative");
for(int i = 0; i < E; i ++){
int a = scanner.nextInt();
validateVertex(a);
int b = scanner.nextInt();
validateVertex(b);
if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");
if(adj[a].contains(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");
adj[a].add(b);
if(directed){//每次添加一个a-b的边,a的出度+1,b的入度+1
outdegrees[a] ++;
indegrees[b] ++;
}
if(!directed)
adj[b].add(a);
}
}
catch(IOException e){
e.printStackTrace();
}
}
public Graph(String filename){
this(filename, false);
}
public boolean isDirected(){
return directed;
}
public void validateVertex(int v){
if(v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + "is invalid");
}
public int V(){
return V;
}
public int E(){
return E;
}
public boolean hasEdge(int v, int w){
validateVertex(v);
validateVertex(w);
return adj[v].contains(w);
}
public Iterable<Integer> adj(int v){
validateVertex(v);
return adj[v];
}
public int degree(int v){//如果是有向图,抛出异常
if(directed)
throw new RuntimeException("degree only works in undirected graph.");
validateVertex(v);
return adj[v].size();
}
public int indegree(int v){
if(!directed)
throw new RuntimeException("indegree only works in directed graph.");
validateVertex(v);
return indegrees[v];
}
public int outdegree(int v){
if(!directed)
throw new RuntimeException("outdegree only works in directed graph.");
validateVertex(v);
return outdegrees[v];
}
public void removeEdge(int v, int w){
validateVertex(v);
validateVertex(w);
if(adj[v].contains(w)){
E --;
if(directed){//删边时维护一下度
indegrees[w] --;
outdegrees[v] --;
}
}
adj[v].remove(w);
if(!directed)
adj[w].remove(v);
}
@Override
public Object clone(){
try{
Graph cloned = (Graph) super.clone();
cloned.adj = new TreeSet[V];
for(int v = 0; v < V; v ++){
cloned.adj[v] = new TreeSet<Integer>();
for(int w: adj[v])
cloned.adj[v].add(w);
}
return cloned;
}
catch (CloneNotSupportedException e){
e.printStackTrace();
}
return null;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(String.format("V = %d, E = %d, directed = %b\n", V, E, directed));
for(int v = 0; v < V; v ++){
sb.append(String.format("%d : ", v));
for(int w : adj[v])
sb.append(String.format("%d ", w));
sb.append('\n');
}
return sb.toString();
}
public static void main(String[] args){
Graph g = new Graph("ug.txt", true);
System.out.print(g);
for(int v = 0; v < g.V(); v ++)
System.out.println(g.indegree(v) + " " + g.outdegree(v));
}
}
有向图求欧拉回路
package com.graph.DirectedGraph.DirectedEulerLoop.src;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Stack;
public class DirectedEulerLoop {
private Graph G;
public DirectedEulerLoop(Graph G){
if(!G.isDirected())
throw new IllegalArgumentException("DirectedEulerLopp only works in directed graph");
this.G = G;
}
private boolean hasEulerLoop(){//判断每个点的入度是否等于出度,暂不判断连通性
for(int v = 0; v < G.V(); v ++)
if(G.indegree(v) != G.outdegree(v))
return false;
return true;
}
public ArrayList<Integer> result(){
ArrayList<Integer> res = new ArrayList<>();
if(!hasEulerLoop()) return res;
Graph g = (Graph)G.clone();
Stack<Integer> stack = new Stack<>();
int curv = 0;
stack.push(curv);
while(!stack.isEmpty()){
if(g.outdegree(curv) != 0){//如果出度不为0,说明从它还有出去的边,就走它
stack.push(curv);
int w = g.adj(curv).iterator().next();
g.removeEdge(curv, w);
curv = w;
}
else{
res.add(curv);
curv = stack.pop();
}
}
Collections.reverse(res);//有向图的顺序必须是正的
return res;
}
public static void main(String[] args){
Graph g = new Graph("ug.txt", true);
DirectedEulerLoop el = new DirectedEulerLoop(g);
System.out.println(el.result());
Graph g2 = new Graph("ug2.txt", true);
DirectedEulerLoop el2 = new DirectedEulerLoop(g2);
System.out.println(el2.result());
}
}
拓扑排序
在实现过程中其实不用真正删点
使用队列来记录当前入度为0的顶点
1,3都可以先进去,一般从大到小入队
拓扑排序不一定有解
拓扑排序进行不下去了,就说明有环。有向无环图DAG才能进行拓扑排序
package com.graph.DirectedGraph.TopologicalSort.src;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class TopoSort {
private Graph G;
private ArrayList<Integer> res;
private boolean hasCycle = false;
public TopoSort(Graph G){
if(!G.isDirected())//拓扑排序只针对有向图有效
throw new IllegalArgumentException("TopoSort only works in directed graph.");
this.G = G;
res = new ArrayList<>();
int[] indegrees = new int[G.V()];//给每个元素的入度分配空间
Queue<Integer> q = new LinkedList<>();//存储还未入队的入度值为0的顶点
for(int v = 0; v < G.V(); v ++){
indegrees[v] = G.indegree(v);//求初始入度值
if(indegrees[v] == 0) q.add(v);//初始入度值为0就放到q里
}
while(!q.isEmpty()){//如果q里不为空,取出q中为0的顶点赋值到cur,加入到res中
int cur = q.remove();
res.add(cur);
for(int next: G.adj(cur)){//遍历cur的每一个相邻顶点
indegrees[next] --;
if(indegrees[next] == 0) q.add(next);//把相邻的,--后度为0的值放入q中,等下一个循环
}
}
if(res.size() != G.V()){//尽管循环终止,如果有环,还是没结束
hasCycle = true;
res.clear();//清楚res,拓扑排序无解
}
}
public boolean hasCycle(){
return hasCycle;
}
public ArrayList<Integer> result(){
return res;
}//返回拓扑排序最终结果
public static void main(String[] args){
Graph g = new Graph("ug.txt", true);
TopoSort topoSort = new TopoSort(g);
System.out.println(topoSort.result());
}
}