题目描述
小明想要处理一批图片,将相似的图片分类。
他首先对图片的特征采样,得到图片之间的相似度,然后按照以下规则判断图片是否可以归为一类:
- 相似度 > 0 表示两张图片相似
- 如果 A 和 B 相似,B 和 C 相似,但 A 和 C 不相似。那么认为A和C间接相似,可以把ABC归为一类,但不计算AC的相似度
- 如果 A 和所有其他图片都不相似,则 A 自己归为一类,相似度为0。
给定一个大小为 N × N 的矩阵 M,存储任意两张图片的相似度:
M[i][j] 即为第 i 个图片和第 j 个图片的相似度
请按照 “从大到小” 的顺序返回每个相似类中所有图片的相似度之和。
输入描述
第一行一个数 N,代表矩阵 M 中有 N 个图片,
下面跟着 N 行,每行有 N 列数据,空格分隔(为了显示整弃,空格可能为多个)代表 N 个图片之间的相似度。
约束:
- 0 < N ≤ 900
- 0 ≤ M[i][j] ≤ 100
- 输入保证 M[i][i] =0,M[i][j]=M[j][i]
输出描述
每个相似类的相似度之和。
格式为:一行数字,分隔符为1个空格。
用例
输入 | 5 0 0 50 0 0 0 0 0 25 0 50 0 0 0 15 0 25 0 0 0 0 0 15 0 0 |
输出 | 65 25 |
说明 | 把1~5看成A,B,C,D,E,矩阵显示,
划分出2个相似类,分别为
排序输出相似度之和,结果为:65 25 |
题目解析
题目用例图示:
本题的图片相似归类可以使用并查集实现,关于并查集知识可以看下:
《算法训练营》进阶篇 01 并查集_哔哩哔哩_bilibili《算法训练营集训》进阶篇全套视频100集在这里: https://www.bilibili.com/cheese/play/ss6658 加QQ群281607840下载所有源码。《算法训练营》是一套没有编程经验的小白也能看懂的算法书,海量图解,实例丰富,图文并茂,全面系统搭建数据结构与算法知识体系,模块化逐一拆解算法问题。300道竞赛试题展示算法设计与实现的详细过程,培养算法思维,感受算法之美。, 视频播放量 17090、弹幕量 20、点赞数 428、投硬币枚数 265、收藏人数 588、转发人数 68, 视频作者 算法训练营, 作者简介 小玉老师,高级程序员,大数据分析师,课程咨询qq155170962,著作《算法训练营》(入门篇、进阶篇)《趣学算法》《趣学数据结构》,相关视频:全网最清晰的并查集讲解,算法讲解056【必备】并查集-上,图论——并查集(详细版),《算法训练营》进阶篇 04 最近公共祖先,[PTA] 朋友圈 并查集,【算法】并查集(Disjoint Set)[共3讲],算法动画秒懂并查集,《算法训练营》进阶篇 02 优先队列,并查集,并查集 画图详解+代码书写https://www.bilibili.com/video/BV1VM4y1K7FV/?spm_id_from=333.337.search-card.all.click
以及练手题目:
LeetCode - 547 省份数量_return (this.fa[x]=this.find(this.fa[x]));-CSDN博客https://fcqian.blog.csdn.net/article/details/127605092LeetCode - 200 岛屿数量_leetcode200岛屿数量-CSDN博客https://fcqian.blog.csdn.net/article/details/127606569
本题难点并不在实现并查集,以及完成节点关联。
而是在节点关联完成后,如何统计每个连通图中所有图片的相似度之和?
如果是暴力求解的话,我们需要将连通图中图片两两判断,累加相似度之和。这种做法并不好。
更优的策略是,在我们遍历M矩阵时,如果M[i][j] > 0,则说明 i, j 图片相似,相似度为M[i][j]。
此时,我们需要做两个操作:
- 完成 i,j 节点的关联,即并查集的union操作
- 将此时的相似度 M[i][j] 记录到 i, j 关联后的新连通图的根上
另外,需要注意的是,在完成 i,j 关联之前
- fa[i] 节点记录 i 所在连通图中所有图片的相似度之和
- fa[j] 节点记录 j 所在连通图中所有图片的相似度之和
当完成关联后,i,j处于一个连通图中,假设fa[j] = fa[i],即将 j 所在子树并入了 i 所在子树,此时新连通图的根为fa[i]。
此时我们需要注意,由于我们期望将连通图中所有图片的相似度之和累计到根上,因此此时
- fa[i] += fa[j]
- fa[j] = 0
JS算法源码
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;void (async function () {const n = parseInt(await readline());const matrix = [];for (let i = 0; i < n; i++) {matrix.push((await readline()).split(/\s+/).map(Number));}const ufs = new UnionFindSet(n);for (let i = 0; i < n; i++) {for (let j = i + 1; j < n; j++) {const similar = matrix[i][j];if (similar > 0) {// 合并两个子连通图为一个,将所有相似度之和(包括本次similar)全部转移到新连通图的根节点上ufs.union(i, j, similar);}}}// 按照 “从大到小” 的顺序返回每个相似类中所有图片的相似度之和const ans = ufs.sum.sort((a, b) => b - a).filter((a) => a > 0).join(" ");console.log(ans);
})();class UnionFindSet {constructor(n) {this.fa = new Array(n).fill(true).map((_, idx) => idx);// sum[i]表示以i为根的相似类中所有图片的相似度之和this.sum = new Array(n).fill(0);}find(x) {while (x !== this.fa[x]) {x = this.fa[x];}return x;}union(x, y, val) {let x_fa = this.find(x);let y_fa = this.find(y);// 本次新增的相似度,归到x_fa根或者y_fa根都可以this.sum[x_fa] += val;if (x_fa !== y_fa) {// 让Y_fa指向x_fa, 即x_fa成为新根this.fa[y_fa] = x_fa;// 此时y_fa上的相似度之和累计到新根x_fa上this.sum[x_fa] += this.sum[y_fa];// 累计完后,y_fa不在记录相似度this.sum[y_fa] = 0;}}
}
Java算法源码
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();UnionFindSet ufs = new UnionFindSet(n);for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {int similar = sc.nextInt();// 避免重复ufs.union操作if (j <= i) continue;if (similar > 0) {// 合并两个子连通图为一个,将所有相似度之和(包括本次similar)全部转移到新连通图的根节点上ufs.union(i, j, similar);}}}StringJoiner sj = new StringJoiner(" ");// 升序Arrays.sort(ufs.sum);// 按照 “从大到小” 的顺序返回每个相似类中所有图片的相似度之和for (int i = n - 1; i >= 0; i--) {if(ufs.sum[i] == 0) break;sj.add(ufs.sum[i] + "");}System.out.println(sj);}
}// 并查集实现
class UnionFindSet {int[] fa;// sum[i]表示以i为根的相似类中所有图片的相似度之和int[] sum;public UnionFindSet(int n) {this.fa = new int[n];for (int i = 0; i < n; i++) fa[i] = i;this.sum = new int[n];}public int find(int x) {if (x != this.fa[x]) {this.fa[x] = this.find(this.fa[x]);return this.fa[x];}return x;}public void union(int x, int y, int val) {int x_fa = this.find(x);int y_fa = this.find(y);// 本次新增的相似度,归到x_fa根或者y_fa根都可以this.sum[x_fa] += val;if (x_fa != y_fa) {// 让Y_fa指向x_fa, 即x_fa成为新根this.fa[y_fa] = x_fa;// 此时y_fa上的相似度之和累计到新根x_fa上this.sum[x_fa] += this.sum[y_fa];// 累计完后,y_fa不在记录相似度this.sum[y_fa] = 0;}}
}
Python算法源码
import re# 并查集实现
class UnionFindSet:def __init__(self, n):self.fa = [i for i in range(n)]# sum[i]表示以i为根的相似类中所有图片的相似度之和self.sum = [0 for i in range(n)]def find(self, x):if x != self.fa[x]:self.fa[x] = self.find(self.fa[x])return self.fa[x]return xdef union(self, x, y, val):x_fa = self.find(x)y_fa = self.find(y)# 本次新增的相似度,归到x_fa根或者y_fa根都可以self.sum[x_fa] += valif x_fa != y_fa:# 让Y_fa指向x_fa, 即x_fa成为新根self.fa[y_fa] = x_fa# 此时y_fa上的相似度之和累计到新根x_fa上self.sum[x_fa] += self.sum[y_fa]# 累计完后,y_fa不在记录相似度self.sum[y_fa] = 0# 算法入口
def solution():n = int(input())matrix = []for _ in range(n):matrix.append(list(map(int, re.split(r"\s+", input()))))ufs = UnionFindSet(n)for i in range(n):for j in range(i + 1, n):similar = matrix[i][j]if similar > 0:# 合并两个子连通图为一个,将所有相似度之和(包括本次similar)全部转移到新连通图的根节点上ufs.union(i, j, similar)# 按照 “从大到小” 的顺序返回每个相似类中所有图片的相似度之和ufs.sum.sort(reverse=True)return " ".join(map(str, filter(lambda x: x > 0, ufs.sum)))# 算法调用
print(solution())
C算法源码
#include <stdio.h>
#include <stdlib.h>/** 并查集定义 **/
typedef struct {int *fa;// sum[i]表示以i为根的相似类中所有图片的相似度之和int *sum;
} UFS;UFS *new_UFS(int n) {UFS *ufs = (UFS *) malloc(sizeof(UFS));ufs->fa = (int *) malloc(sizeof(int) * n);ufs->sum = (int *) malloc(sizeof(int) * n);for (int i = 0; i < n; i++) {ufs->fa[i] = i;ufs->sum[i] = 0;}return ufs;
}int find_UFS(UFS *ufs, int x) {if (x != ufs->fa[x]) {ufs->fa[x] = find_UFS(ufs, ufs->fa[x]);return ufs->fa[x];}return x;
}void union_UFS(UFS *ufs, int x, int y, int val) {int x_fa = find_UFS(ufs, x);int y_fa = find_UFS(ufs, y);// 本次新增的相似度,归到x_fa根或者y_fa根都可以ufs->sum[x_fa] += val;if (x_fa != y_fa) {// 让Y_fa指向x_fa, 即x_fa成为新根ufs->fa[y_fa] = x_fa;// 此时y_fa上的相似度之和累计到新根x_fa上ufs->sum[x_fa] += ufs->sum[y_fa];// 累计完后,y_fa不在记录相似度ufs->sum[y_fa] = 0;}
}int cmp(const void *a, const void *b) {return *((int *) b) - *((int *) a);
}int main() {int n;scanf("%d", &n);UFS *ufs = new_UFS(n);for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {int similar;scanf("%d", &similar);if (j <= i) continue;if (similar > 0) {// 合并两个子连通图为一个,将所有相似度之和(包括本次similar)全部转移到新连通图的根节点上union_UFS(ufs, i, j, similar);}}}// 按照 “从大到小” 的顺序返回每个相似类中所有图片的相似度之和qsort(ufs->sum, n, sizeof(int), cmp);for (int i = 0; i < n; i++) {if (ufs->sum[i] == 0) break;printf("%d ", ufs->sum[i]);}puts("");return 0;
}
C++算法源码
#include <bits/stdc++.h>
using namespace std;// 并查集实现
class UnionFindSet {
public:int *fa;// sum[i]表示以i为根的相似类中所有图片的相似度之和int *sum;explicit UnionFindSet(int n) {fa = new int[n];sum = new int[n];for (int i = 0; i < n; i++) {fa[i] = i;sum[i] = 0;}};int find(int x) {if (x != fa[x]) {fa[x] = find(fa[x]);return fa[x];}return x;};void merge(int x, int y, int val) {int x_fa = find(x);int y_fa = find(y);// 本次新增的相似度,归到x_fa根或者y_fa根都可以sum[x_fa] += val;if (x_fa != y_fa) {// 让Y_fa指向x_fa, 即x_fa成为新根fa[y_fa] = x_fa;// 此时y_fa上的相似度之和累计到新根x_fa上sum[x_fa] += sum[y_fa];// 累计完后,y_fa不在记录相似度sum[y_fa] = 0;}};
};int main() {int n;cin >> n;UnionFindSet ufs(n);for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {int similar;cin >> similar;if (j <= i) continue;if (similar > 0) {// 合并两个子连通图为一个,将所有相似度之和(包括本次similar)全部转移到新连通图的根节点上ufs.merge(i, j, similar);}}}// 升序sort(ufs.sum, ufs.sum + n);// 按照 “从大到小” 的顺序返回每个相似类中所有图片的相似度之和for (int i = n - 1; i >= 0; i--) {if (ufs.sum[i] == 0) break;cout << ufs.sum[i] << " ";}cout << endl;return 0;
}