package _09_图;
import java.util.*;
/**
* 输入格式:
* V, E : V个顶点, 1-V, E条边
* from
* to
* weight
*/
public class MyGraphy {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//构造图
int v = sc.nextInt();
int e = sc.nextInt();
Graphy g = new Graphy(v, e);
int start, end, weight;
//输入按[1,V]输入, 保存时-1按[0,V-1]保存
for (int i = 0; i < e; i++) {
g.start[i] = sc.nextInt() - 1;
}
for (int i = 0; i < e; i++) {
g.end[i] = sc.nextInt() - 1;
}
for (int i = 0; i < e; i++) {
g.weight[i] = sc.nextInt();
}
g.init();
// System.out.println(g.prim());
// System.out.println(g.Kruskal());
// g.Dijkstra(1 - 1);
// g.BellmanFord(1 - 1);
// System.out.printf("A->C的最短路径为%d\n", g.Floyd(1 - 1, 3 - 1));
// System.out.println(g.topologicalSort().toString());
g.dfs(1 - 1);
System.out.println();
g.bfs(1 - 1);
}
static class Graphy {
int vertexSize; //顶点个数
int edgeSize; //边的条数
//存放接收数据
int[] start;
int[] end;
int[] weight;
Set<Edge>[] from; //存放顶点的入边
Set<Edge>[] to; //存放顶点的出边
int[] path; //存放最短路径的路径信息
int[] dist; //存放最短路径的路径长度
boolean[] used; //标记顶点是否访问过
public Graphy(int vertexSize, int edgeSize) {
this.vertexSize = vertexSize;
this.edgeSize = edgeSize;
//初始化每个顶点对应的出入边
from = new HashSet[vertexSize];
to = new HashSet[vertexSize];
//初始化最短路径的路径信息和路径长度
dist = new int[vertexSize];
path = new int[vertexSize];
//按从0~V-1存放
for (int i = 0; i < vertexSize; i++) {
from[i] = new HashSet<>();
to[i] = new HashSet<>();
dist[i] = Integer.MAX_VALUE;
path[i] = -1;
}
//初始化标记数组
used = new boolean[vertexSize];
//初始化边的信息
start = new int[edgeSize];
end = new int[edgeSize];
weight = new int[edgeSize];
}
public void bfs(int begin) {
clear();
Set<Edge> edges;
Queue<Set<Edge>> queue = new LinkedList<>();
queue.offer(to[begin]);
used[begin] = true;
System.out.println(begin + 1);
while (!queue.isEmpty()) {
edges = queue.poll();
for (Edge edge : edges) {
if (used[edge.to])
continue;
queue.offer(to[edge.to]);
used[edge.to] = true;
System.out.println(edge.to + 1);
}
}
}
public void dfs(int begin) {
System.out.println(begin + 1);
used[begin] = true;
for (Edge edge : to[begin]) {
if (used[edge.to])
continue;
dfs(edge.to);
}
}
/**
* 拓扑排序
*/
public List<Integer> topologicalSort() {
//保存返回结果
List<Integer> list = new ArrayList<>();
//存放当前入度为0的顶点, 会动态更新
Queue<Set<Edge>> queue = new LinkedList<>();
//存放各个顶点对应的入读, 会动态更新
int[] fromSize = new int[vertexSize];
//顶点的出边集合, 存放在队列中
Set<Edge> outEdges;
//初始化为-1
Arrays.fill(fromSize, -1);
//遍历当前顶点的入度信息
for (int i = 0; i < vertexSize; i++) {
//将入度为0的压入队列, 并更新结果
if (from[i].size() == 0) {
queue.offer(to[i]);
list.add(i + 1);
} else {
//否则记录其入度
fromSize[i] = from[i].size();
}
}
while (!queue.isEmpty()) {
//抛出队头顶点的出边
outEdges = queue.poll();
//判断其出边to的入度
for (Edge edge : outEdges) {
//如果为1, 则删除当前顶点后其入度为0
if (fromSize[edge.to] == 1) {
queue.offer(to[edge.to]);
list.add(edge.to + 1);
} else {
//否则更新to的入度信息
fromSize[edge.to]--;
}
}
}
return list;
}
/**
* 城市总距离最小值, 成本最小值, 最小生成树
* Kruskal简单
*/
/**
* 将当前顶点的所有边加入最小堆, 每次取出一个最小权值边
* 当前边没有访问过, 则标记from, to
* 否则舍弃
*/
public int prim() {
clear();
//要返回的最小值
int min = 0;
//默认以0位起始顶点
int usedSize = 1; //判断是否所有顶点都被标记过, 结束搜索
//利用最小堆来获取权值最小的边
PriorityQueue<Edge> minHeap = new PriorityQueue<>(((o1, o2) -> o1.weight - o2.weight));
//将顶点1的边入堆
minHeap.addAll(to[0]);
while (!minHeap.isEmpty() && usedSize < vertexSize) {
//取出堆中当前最小权值边
Edge edge = minHeap.remove();
//判断出边邻接顶点是否标记过
if (used[edge.from] && used[edge.to])
continue;
//更新最小值
min += edge.weight;
System.out.println(edge);
//更新标记过的顶点数
usedSize++;
//更新出边邻接顶点标记
used[edge.from] = true;
used[edge.to] = true;
//将出边邻接顶点的出边入堆
minHeap.addAll(from[edge.to]);
minHeap.addAll(to[edge.to]);
}
return min;
}
/**
* 将所有的边加入最小堆, 每次取出最小权值边
* 利用并查集判断是否有环路, 有则放弃当前边
*/
public int Kruskal() {
int min = 0; //记录返回结果
int edgeSize = 0; //保存标记的边的条数, ==V-1时结束搜索
//利用最小堆选取最小权值边
PriorityQueue<Edge> minHeap = new PriorityQueue<>(((o1, o2) -> o1.weight - o2.weight));
//将所有边入堆
for (Set<Edge> edges : to) {
minHeap.addAll(edges);
}
//利用并查集判断当前是否存在环
UF uf = new UF(vertexSize);
while (!minHeap.isEmpty() && edgeSize < vertexSize - 1) {
//拿到最小权值边
Edge edge = minHeap.remove();
//如果存在环则舍弃
if (uf.isSame(edge.from, edge.to))
continue;
//否则合并from,to
uf.union(edge.from, edge.to);
//更新最小结果
min += edge.weight;
}
return min;
}
/**
* 最短路径问题
* */
/**
* 一个顶点到其他所有顶点的最短路径和
* 不能用负权边
*/
public void Dijkstra(int vertex) {
clear(); //清空标记数组
int v; //记录当前节点
int size; //记录已标记的顶点数, ==V时停止
int min; //记录搜索的最小顶点索引
int minDist; //记录搜索到的最小顶点路径值
//标记起始顶点
used[vertex] = true;
//更新标记数
size = 1;
//将起始节点的出边更新到dist和path中去, 默认起始节点from为空
for (Edge edge : to[vertex]) {
v = edge.to;
path[v] = vertex;
dist[v] = edge.weight;
}
//标记数!=V, 循环继续
while (size < vertexSize) {
minDist = Integer.MAX_VALUE;
min = -1;
//遍历dist, 找到当前到下一个未标记顶点路径最小的顶点
for (int i = 0; i < vertexSize; i++) {
if (!used[i] && dist[i] < minDist) {
minDist = dist[i];
min = i;
}
}
//标记当前顶点
used[min] = true;
size++;
//利用最小顶点更新出边信息
for (Edge edge : to[min]) {
v = edge.to;
if (!used[v] && minDist + edge.weight < dist[v]) {
dist[v] = minDist + edge.weight;
path[v] = min;
}
}
//更新入边信息
for (Edge edge : from[min]) {
v = edge.from;
if (!used[v] && minDist + edge.weight < dist[edge.from]) {
dist[v] = minDist + edge.weight;
path[v] = min;
}
}
}
System.out.println("------------Dijkstra-----------");
prt(dist, path, vertex);
}
/**
* 支持负权边, 还能检测出是否有负权环
* 对所有边最多进行V-1次松弛操作即可
* 针对有向图
*/
public void BellmanFord(int vertex) {
clear(); //清空标记数组
//最大循环次数
int count = vertexSize - 1;
//标记起始顶点
used[vertex] = true;
dist[vertex] = 0;
int from;
int to;
int weight;
//最多循环V-1次
while (count-- > 0) {
//遍历所有边
for (int i = 0; i < edgeSize; i++) {
from = start[i];
to = end[i];
weight = this.weight[i];
//如果当前边的from没有标记过, 则无法更新to
if (!used[from])
continue;
relax(from, to, weight);
}
}
//遍历所有边
for (int i = 0; i < edgeSize; i++) {
from = start[i];
to = end[i];
weight = this.weight[i];
//如果当前边的from没有标记过, 则无法更新to
if (!used[from])
continue;
//如果再次进行了松弛操作, 则有负权环
if (relax(from, to, weight)) {
System.out.println("有负权环");
return;
}
}
System.out.println("------------BallmanFord-----------");
prt(dist, path, vertex);
}
//松弛操作
private boolean relax(int from, int to, int weight) {
//如果to已经标记过, 且当前新的长度>=原有路径长度, 则退出
if (used[to] && dist[from] + weight >= dist[to])
return false;
//此时有两种情况
//1. to没有被标记过
//2. to被标记过, 且路径值需要更新
used[to] = true;
dist[to] = dist[from] + weight;
path[to] = from;
return true;
}
/**
* 可以求出任意两个顶点的最短路径, 支持负权边
*/
public int Floyd(int a, int b) {
//V*V大小数组, 类似DP
int[][] dists = new int[vertexSize][vertexSize];
//初始化为-1
for (int i = 0; i < vertexSize; i++) {
Arrays.fill(dists[i], -1);
}
//将当前的边导入dists
for (int i = 0; i < edgeSize; i++) {
dists[start[i]][end[i]] = weight[i];
}
//判断dists[i][k]+dists[k][j]<dists[i][j]
for (int k = 0; k < vertexSize; k++) {
for (int i = 0; i < vertexSize; i++) {
for (int j = 0; j < vertexSize; j++) {
int p1 = dists[i][k];
int p2 = dists[k][j];
int p3 = dists[i][j];
//如果路径信息不存在, 则无法更新
if (p1 == -1 || p2 == -1)
continue;
//如果原来有路径信息且小于新的路径信息, 则不更新
if (p3 != -1 && p1 + p2 >= p3)
continue;
//更新路径信息
dists[i][j] = p1 + p2;
}
}
}
//输出所有两个顶点间的最短路径
// for (int i = 0; i < vertexSize; i++) {
// for (int j = i + 1; j < vertexSize; j++) {
// System.out.printf("%c->%c的最短路径为%d\n", 'A' + i, 'A' + j, dists[i][j]);
// }
// }
return dists[a][b];
}
/**
* 输出最小路径信息
*/
public void prt(int[] dist, int[] path, int vertex) {
//倒序输出vertex到其余所有顶点的路径信息
for (int i = 0; i < vertexSize; i++) {
if (i == vertex)
continue;
int tmp = i;
while (path[tmp] != -1) {
System.out.printf("v%d->", tmp + 1);
tmp = path[tmp];
}
System.out.printf("v1, %d\n", dist[i]);
}
}
/**
* 更新每个顶点的出入边信息
*/
public void init() {
// //有向图
for (int i = 0; i < edgeSize; i++) {
Edge edge = new Edge(start[i], end[i], weight[i]);
to[start[i]].add(edge); //出边放入该顶点的to
from[end[i]].add(edge); //入边放入该顶点的from
}
//无向图
// for (int i = 0; i < edgeSize; i++) {
// Edge edge = new Edge(start[i], end[i], weight[i]);
// from[start[i]].add(edge);
// from[end[i]].add(edge);
// }
}
/**
* 清空标记数组
*/
private void clear() {
Arrays.fill(used, false);
Arrays.fill(dist, Integer.MAX_VALUE);
Arrays.fill(path, -1);
}
}
static class Edge {
int from;
int to;
int weight;
public Edge() {
}
public Edge(int from, int to, int weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
@Override
public String toString() {
return "Edge{" +
"from=" + from +
", to=" + to +
", weight=" + weight +
'}';
}
}
static class UF {
int size;
int[] parents;
int[] ranks;
public UF(int size) {
this.size = size;
parents = new int[size];
for (int i = 0; i < size; i++)
parents[i] = i;
ranks = new int[size];
}
public void union(int x, int y) {
int a = find(x);
int b = find(y);
if (a == b)
return;
if (ranks[a] > ranks[b]) {
parents[b] = a;
} else if (ranks[a] < ranks[b]) {
parents[a] = b;
} else {
parents[b] = a;
ranks[a]++;
}
}
public boolean isSame(int x, int y) {
return find(x) == find(y);
}
public int find(int x) {
while (x != parents[x]) {
parents[x] = parents[parents[x]];
x = parents[x];
}
return x;
}
}
}
图论相关算法实现,Java
最新推荐文章于 2024-04-30 11:03:13 发布