java图的基础算法
遍历器的实现
public class Iterator {
//传入顶点坐标
// 返回图中一个顶点的所有邻边
// 由于java使用引用机制,返回一个Vector不会带来额外开销,
public Iterable<Integer> adj(int v) {
assert v >= 0 && v < n;
return g[v];
}
// 返回图中一个顶点的所有邻边
// 由于java使用引用机制,返回一个Vector不会带来额外开销,
public Iterable<Integer> adj(int v) {
assert v >= 0 && v < n;
Vector<Integer> adjV = new Vector<Integer>();
for(int i = 0 ; i < n ; i ++ )
if( g[v][i] )
adjV.add(i);
//返回遍历器与遍历对象
return adjV;
}
//根据遍历器获取遍历的值
for( int i: G.adj(v) ){
if( !visited[i] )
dfs(i);
}
}
图深度优先获取图的连通分量
public class Components {
private int[] id; // 每个节点所对应的联通分量标记
Graph G; // 图的引用
private boolean[] visited; // 记录dfs的过程中节点是否被访问
private int ccount; // 记录联通分量个数
//从一个点开始找与它相邻的点,然后从这个点不停的往下尝试,直到尝试不下去这条路就已经走完了,退回到一开始的节点
void dfs( int v ){
visited[v] = true;
id[v] = ccount;
for( int i: G.adj(v) ){
if( !visited[i] )
dfs(i);
}
}
// 算法初始化
G = graph;
visited = new boolean[G.V()];
id = new int[G.V()];
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);
ccount ++;
}
}
}
寻路
public class Path {
private Graph G; // 图的引用
private int s; // 起始点
private boolean[] visited; // 记录dfs的过程中节点是否被访问
private int[] from; // 记录路径, from[i]表示查找的路径上i的上一个节点
visited = new boolean[G.V()];
from = new int[G.V()];
for( int i = 0 ; i < G.V() ; i ++ ){
visited[i] = false;
from[i] = -1;
}
//遍历的同时存是从哪个节点过来的
// 图的深度优先遍历
private void dfs( int v ){
visited[v] = true;
for( int i : G.adj(v) )
if( !visited[i] ){
from[i] = v;
dfs(i);
}
}
//倒推出从0到任意节点的路径
// 查询从s点到w点的路径, 存放在vec中
Vector<Integer> path(int w){
assert hasPath(w) ;
Stack<Integer> s = new Stack<Integer>();
// 通过from数组逆向查找到从s到w的路径, 存放到栈中
int p = w;
while( p != -1 ){
s.push(p);
p = from[p];
}
// 从栈中依次取出元素, 获得顺序的从s到w的路径
Vector<Integer> res = new Vector<Integer>();
while( !s.empty() )
res.add( s.pop() );
return res;
}
}
广度优先与最短路径
public class ShortestPath {
private Graph G; // 图的引用
private int s; // 起始点
private boolean[] visited; // 记录dfs的过程中节点是否被访问
private int[] from; // 记录路径, from[i]表示查找的路径上i的上一个节点
private int[] ord; // 记录路径中节点的次序。ord[i]表示i节点在路径中的次序。
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;
// 无向图最短路径算法, 从s开始广度优先遍历整张图
Queue<Integer> q = new LinkedList<Integer>();
q.add(s);
visited[s] = true;
ord[s] = 0;
while( !q.isEmpty() ){
//从队列中取出元素相当于遍历了这个节点,一次将图中所有相邻的节点全部遍历
int v = q.remove();
for( int i : G.adj(v) )
//再去遍历相邻节点的相邻节点,加入到队列的元素不用再遍历了
if( !visited[i] ){
q.add(i);
visited[i] = true;
from[i] = v;
ord[i] = ord[v] + 1;
}
}
}
// 查询从s点到w点是否有路径
public boolean hasPath(int w){
assert w >= 0 && w < G.V();
return visited[w];
}
// 查询从s点到w点的路径, 存放在vec中
public Vector<Integer> path(int w){
assert hasPath(w) ;
Stack<Integer> s = new Stack<Integer>();
// 通过from数组逆向查找到从s到w的路径, 存放到栈中
int p = w;
while( p != -1 ){
s.push(p);
p = from[p];
}
// 从栈中依次取出元素, 获得顺序的从s到w的路径
Vector<Integer> res = new Vector<Integer>();
while( !s.empty() )
res.add( s.pop() );
return res;
}
}
Kruskal计算有权无向图的最小生成树
public class Krusk {
//有权图
static List<List<Double[]>> totallist;
//最小生成树
static List<Double[]> minTree;
static int[] rank;
static int[] parent;
static double mstWeight;
totallist = new ArrayList<>();
minTree = new ArrayList<>();
rank = new int[N];
parent = new int[N];
for(int i=0;i<N;i++) {
rank[i] = 1;
parent[i] = i;
totallist.add(new LinkedList<>());
}
//找到最小队列的中权值最小的一条边
//最小堆
PriorityQueue<Double[]> queue = new PriorityQueue<>(new Comparator<Double[]>() {
@Override
public int compare(Double[] o1, Double[] o2) {
// TODO Auto-generated method stub
return o1[2].compareTo(o2[2]);
}
});
for(int i=0;i<M;i++) {
int s = scanner.nextInt();
double ds = Double.valueOf(s);
int e = scanner.nextInt();
double de = Double.valueOf(e);
double w = scanner.nextDouble();
totallist.get(s).add(new Double[] {ds,de,w});
if(s!=e) {
totallist.get(e).add(new Double[] {de,ds,w});
}
queue.offer(new Double[] {ds,de,w});
/*if(s<e) { //s<=e
queue.offer(new Double[] {ds,de,w});
}*/
}
//判断是否形成环,如果不形成环放入最小生成树
while(!queue.isEmpty()) {
// 从最小堆中依次从小到大取出所有的边
Double[] top = queue.poll();
int startIndex = top[0].intValue();
int toIndex = top[1].intValue();
// 如果该边的两个端点是联通的, 说明加入这条边将产生环, 扔掉这条边
if(isConnected(startIndex, toIndex)) {
continue;
}
// 否则, 将这条边添加进最小生成树, 同时标记边的两个端点联通
minTree.add(top);
unionElement(startIndex,toIndex);
}
mstWeight = minTree.get(0)[2];
for(int i=1;i<minTree.size();i++) {
mstWeight = mstWeight+minTree.get(i)[2];
}
System.out.println(mstWeight);
}
private static boolean isConnected(int v,int w) {
return find(v)==find(w);
}
private static int find(int v) {
// TODO Auto-generated method stub
while(v!=parent[v]) {
parent[v] = parent[parent[v]];
v = parent[v];
}
return v;
}
private static void unionElement(int v,int w) {
int vRoot = find(v);
int wRoot = find(w);
if(vRoot==wRoot) {
return;
}
if(rank[vRoot]<rank[wRoot]) {
parent[vRoot] = wRoot;
}else if(rank[wRoot]<rank[vRoot]) {
parent[wRoot] = vRoot;
}else {
parent[vRoot] =wRoot;
rank[wRoot]+=1;
}
}
}
Dijkstra有向带权图单源最短路径问题
public class Dijkstra {
static List<List<int[]>> totallist;
static int[] distTO; //distTo[i]存储从起始点s到i的最短路径长度
static boolean[] visited;
static int[][] from; //from[i]记录最短路径中, 到达i点的边是哪一条,可以用来恢复整个最短路径
static int N;
static int start;
static final int _INTMAX_ = 2087654321;
N = scanner.nextInt();
int M = scanner.nextInt();
totallist = new ArrayList<>();
for(int i=0;i<N;i++) {
totallist.add(new LinkedList<>());
}
for(int i=0;i<M;i++) {
int s = scanner.nextInt();
int e = scanner.nextInt();
int w = scanner.nextInt();
totallist.get(s).add(new int[] {s,e,w});
//无向图
//totallist.get(e).add(new int[] {e,s,w});
}
//
//如果没有目的地,要执行到最后dijkstra(start,-1);
start = 0;
int dest = -1;
dijkstra(start,dest);
int minRoute = distTO[4];
//访问顶点所有的临边,得到暂时的最短的一条路径
private static void dijkstra(int start, int dest) {
// TODO Auto-generated method stub
distTO = new int[N];
visited = new boolean[N];
from = new int[N][3];
for(int i=0;i<N;i++) {
distTO[i] = _INTMAX_;
}
//最小堆
PriorityQueue<int[]> queue =new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
// TODO Auto-generated method stub
return o1[1]-o2[1];
}
});
distTO[start] = 0;
from[0]= new int[] {start,start,0}; //起始点到起始点的距离为0
queue.add(new int[] {start,0});
//访问从最短的一条路径为中专到其他顶点的路径长度,如果获得更加短的一条路径,则为最短路径
while(!queue.isEmpty()) { //找离起始点最短的边
int[] cur = queue.poll();
visited[cur[0]] = true;
if(cur[0]==dest) break; //找到终点
for(int[] item:totallist.get(cur[0])) {
if(!visited[item[1]]) {
int startIndex = item[0];
int toIndex = item[1];
int weight = item[2];
weight+=cur[1];
if(weight<distTO[toIndex]) {
distTO[toIndex] = weight;
from[toIndex] = item;
queue.add(new int[] {toIndex,weight});
}
}
}
}
}
private static List<int[]> shortestPath(int w){
Deque<int[]> deque = new LinkedList<>();
int[] e = from[w];
while(e[0]!=start) {
deque.push(e);
e = from[e[0]];
}
deque.push(e);
List<int[]> res = new ArrayList<>();
while(!deque.isEmpty()) {
int[] retItem = deque.pop();
res.add(retItem);
}
return res;
}
private static void showPath(int w) {
List<int[]> path = shortestPath(w);
for(int i =0;i<path.size();i++) {
System.out.println(path.get(i)[0]+"->");
if(i==path.size()-1) {
System.out.println(path.get(i)[1]);
}
}
}
}
Bellman-Ford计算负权边单源最短路径
public class BellmanFora {
static List<List<int[]>> totallist;
static int start;
static int[] distTo;
static int[][] from;
static int N;
totallist = new ArrayList<>();
distTo = new int[N];
from = new int[N][3];
for(int i=0;i<N;i++) {
totallist.add(new LinkedList<>());
from[i] = null;
}
for(int i=0;i<M;i++) {
int s = scanner.nextInt();
int e = scanner.nextInt();
int w = scanner.nextInt();
totallist.get(s).add(new int[] {s,e,w});
}
// 设置distTo[start] = 0, 并且让from[start]不为空, 表示初始s节点可达且距离为0
int start = 0;
distTo[start] = 0;
from[start] = new int[] {start,start,0};
//对所有的点进行两次松弛操作,找到从原点开始经过两条边得到的最短路径是否权值更小
// Bellman-Ford的过程
// 进行V-1次循环, 每一次循环求出从起点到其余所有点, 最多使用pass步可到达的最短距离
for(int pass=1;pass<N;pass++) {
// 每次循环中对所有的边进行一遍松弛操作
// 遍历所有边的方式是先遍历所有的顶点, 然后遍历和所有顶点相邻的所有边
for(int k=0;k<N;k++) {
for(int[] item:totallist.get(k)) {
int startIndex = item[0];
int toIndex = item[1];
int weight = item[2];
// 对于每一个边首先判断e->v()可达
// 之后看如果e->w()以前没有到达过, 显然我们可以更新distTo[e->w()]
// 或者e->w()以前虽然到达过, 但是通过这个e我们可以获得一个更短的距离, 即可以进行一次松弛操作, 我们也可以更新distTo[e->w()]
if(from[startIndex]!= null&&(from[toIndex]== null||distTo[startIndex]+weight<distTo[toIndex])) {
distTo[toIndex] = distTo[startIndex]+weight;
from[toIndex] = item;
}
}
}
}
//判断是否有负权边,如果存在从一点到另外一点的最短路径,最多经过V个顶点有V-1条边
//判断是否有负权环
boolean hasNegativeCycle = detectNegativeCycle();
// 返回从s点到w点的最短路径长度
int end = 4;
int minRoute = distTo[end];
System.out.println(hasNegativeCycle+" "+minRoute);
for(int m=1;m<N;m++) {
System.out.println("Shortest Path to " + m + " : " + distTo[m]);
showPath(m);
}
}
// 判断图中是否有负权环
private static boolean detectNegativeCycle() {
// TODO Auto-generated method stub
for(int i =0;i<N;i++) {
for(int[] item:totallist.get(i)) {
if(from[item[0]].length>0&&distTo[item[0]]+item[2]<distTo[item[1]]) {
return true;
}
}
}
return false;
}
}