java生成树代码_最小生成树 java实现

最小生成树

最小生成树的定义:最小生成树是在一个给定的无向图G(V,E)中求一颗树T,使得这棵树拥有图G中所有顶点,且所有边都是来自图G中的边,并且满足整棵树的边权之和最小。

最小生成树三个性质:

最小生成树是树,因此其边数等于顶点数减一,且树内一定不会有环

对给定的图,其最小生成树可以不唯一,但边权之和一定唯一

最小生成树是在无向图上面生成的,因此其根节点可以是这颗树上面的任意一个节点。如果题目中涉及最小生成树的输出,为了让生成树唯一,一般会直接给出根节点,只需要给出节点作为根节点来求解最小生成树即可

问题种类:铺设公路

两种解决算法

普利姆算法(prim)

朴素版Prim算法(稠密图),时间复杂度O(n^2)

堆优化版Prim算法(稀疏图),时间 复杂度O(mlogn)(很少使用)

克鲁斯卡尔算法(Kruskal)(稀疏图)

朴素版prim算法

假如有5个节点,6条边,

A B 3

A C 1

B C 2

A E 4

C D 5

C E 6

T 表示当前包含所有节点的集合,U 表示NULL。假如从A开始,(将A加入到U中)

第一步:选中A到各个节点中权重最小的节点

第二步:判断该节点是否被访问过,如果没有被访问过,则将该节点加入到集合U中

第三步:更新其他节点到集合U的距离

第四步:选择到集合U最近的点,重复第二步

时间复杂度是 \(O(n2+m)\), n 表示点数,m 表示边数

static int n; // n表示点数

static int INF = 0x3f3f3f3f;

static int[][] g = new int[N][N]; // 邻接矩阵,存储所有边

static int[] dist = new int[N]; // 存储其他点到当前最小生成树(集合)的距离

static boolean st = new int[N]; // 存储每个点是否已经在生成树中

// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和

int prim(){

Arrays.fill(dist, 0x3f);

//所有生成树里的边的长度之和的最小值

int res = 0;

for (int i = 0; i < n; i ++ ){

int t = -1;

for (int j = 1; j <= n; j ++ ) {

if (st[j] != false && (t == -1 || dist[t] > dist[j])) {

t = j;

}

}

if (i != 0 && dist[t] == INF) return INF;

if (i != 0) res += dist[t];

st[t] = true;

for (int j = 1; j <= n; j ++ ) {

dist[j] = min(dist[j], g[t][j]);

}

}

return res;

}

对应题目

/**

*prime算法实现

* 输入样例:

* 4 5

* 1 2 1

* 1 3 2

* 1 4 3

* 2 3 2

* 3 4 4

* 输出样例:

* 6

*/

public class PrimAlgorithm {

int INF = 500000;

private Scanner sc = new Scanner(System.in);

public void run() {

int n = sc.nextInt();

int m = sc.nextInt();

int[][] graph = new int[n + 1][n + 1];

for (int i = 0; i < n + 1; i++) {

Arrays.fill(graph[i], INF);

}

for (int i = 0; i < m; i++) {

int x = sc.nextInt();

int y = sc.nextInt();

int z = sc.nextInt();

//无向图

graph[x][y] = Math.min(graph[x][y], z);

graph[y][x] = graph[x][y];

graph[x][x] = 0;

graph[y][y] = 0;

}

int res = prim(graph);

if (res == INF) {

System.out.println("impossible");

} else {

System.out.println(res);

}

}

public int prim(int[][] graph) {

// graph长度是n+1

int n = graph.length - 1;

// dist[i] 表示的是i到生成树集合的最小距离

int[] dist = new int[n + 1];

// 给dist初始化为无穷

Arrays.fill(dist, INF);

int res = 0;

// 存储对应的边是否被访问过

boolean[] st = new boolean[n + 1];

// 因为之前存储graph的时候,是从下标1开始的,所以这里遍历时候就是从1开始

for (int i = 1; i < n + 1; i++) {

int t = -1;

for (int j = 1; j < n + 1; j++) {

if (st[j] == false && (t == -1 || dist[t] > dist[j])) t = j;

}

// 避免第一次循环的时候,直接返回

if (i != 1 && dist[t] == INF) return INF;

// 表示节点t已经被访问过

st[t] = true;

if (i != 1) {

// 将节点添加到生成树中

res += dist[t];

// System.out.println(res);

}

// 生成树是一个集合

// 因为生成树添加了新的节点,所以更新其他未被访问的点到生成树的最小距离,

for (int j = 1; j < n + 1; j++) {

dist[j] = Math.min(dist[j], graph[t][j]);

}

}

return res;

}

public static void main(String[] args) {

new PrimAlgorithm().run();

}

}

Kruskal算法

前置知识:排序和并查集

基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路。

具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止。

时间复杂度是 \(O(mlogm)\), n 表示点数,m 表示边数

思路:

将所有边按权重w从小到大排序;

枚举每条边的顶点a,b

if (a,b 不在同一个集合中(并查集)) 将这条边加入集合中

int n, m; // n是点数,m是边数

int[] p = new int[N]; // 并查集的父节点数组

class Edge{ // 存储边

int a, b, w;

Edge(int a, int b, int c) {

this.a = a;this.b = b; this.c = c;

}

};

int find(int x){ // 并查集核心操作

if (p[x] != x) p[x] = find(p[x]);

return p[x];

}

public int kruskal(){

Arrays.sort(edge, new Comparator() {

@Override

public int compare(Edge o1, Edge o2) {

if(o1.w

else if(o1.w>o2.w) return 1;

else return 0;

}

});

for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集

int res = 0, cnt = 0;

for (int i = 0; i < m; i ++ ) {

int a = edges[i].a, b = edges[i].b, w = edges[i].w;

a = find(a);

b = find(b);

if (a != b) { // 如果两个连通块不连通,则将这两个连通块合并

p[a] = b;

res += w;

cnt ++ ;

}

}

if (cnt < n - 1) return INF;

return res;

}

题目:

/**

* kruskal算法实现

* 输入样例:

* 4 5

* 1 2 1

* 1 3 2

* 1 4 3

* 2 3 2

* 3 4 4

* 输出样例:

* 6

*/

public class KruskalAlgorithm {

int INF = Integer.MAX_VALUE / 2;

private Scanner sc = new Scanner(System.in);

private int n;

private int m;

public static void main(String[] args) {

new KruskalAlgorithm().run();

}

// 并查集

private int find(int[] p, int x) {

if (p[x] != x) p[x] = find(p, p[x]);

return p[x];

}

private void run() {

n = sc.nextInt();

m = sc.nextInt();

// 存储边

List edgeList = new ArrayList<>();

// 下标从1开始,因为节点的值是从1开始的

int[] p = new int[n + 1];

for (int i = 1; i <= n; i++) {

p[i] = i;

}

for (int i = 0; i < m; i++) {

int x = sc.nextInt();

int y = sc.nextInt();

int z = sc.nextInt();

edgeList.add(new Edge(x, y, z));

}

int res = kruskal(edgeList, p);

if (res == INF) {

System.out.println("impossible");

} else {

System.out.println(res);

}

}

private int kruskal(List edgeList, int[] p) {

int res = 0;

int cnt = 0;

// 按照w 权重的大小来排序

Collections.sort(edgeList);

for (int i = 0; i < edgeList.size(); i++) {

int a = edgeList.get(i).getA();

int b = edgeList.get(i).getB();

int w = edgeList.get(i).getW();

// 分别找到a、b对应的集合的根

a = find(p, a);

b = find(p, b);

// 如果根相同表示a、b在同一个集合中,如果添加进去,会形成回路,则该边不能添加到集合中

// 如果根不相同表示a、b不在同一个集合中,则该边能添加到集合中

if (a != b) {

p[a] = b;

res += w;

// 计算添加进去的边

cnt++;

}

}

// n 是点的数量,n个点最少是n-1条边,如果少于n-1条边,则表示 n个点并没有全部使用到,所以不成立

if (cnt < n - 1) return INF;

return res;

}

}

class Edge implements Comparable {

int a, b, w;

public Edge(int a, int b, int w) {

// a,b 为节点

this.a = a;

this.b = b;

// w 为节点之间的权重

this.w = w;

}

public int getA() {

return a;

}

public int getB() {

return b;

}

public int getW() {

return w;

}

// 设置edge的排序规则,按照w的大小排序

@Override

public int compareTo(Edge edge) {

return Integer.compare(w, edge.w);

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值