题解 | #实体匹配结果归并与排序#
实体匹配结果归并与排序
https://www.nowcoder.com/practice/bc854ebbf5454e7b93e8d7f90faf1524
REALHW82 实体匹配结果归并与排序
题目链接
题目描述
在数据治理平台中,不同的实体匹配引擎会各自产出“被认为指向同一真实实体”的编号集合。每一行输入代表某个引擎得到的一组编号(集合),行内的编号可能有重复。若两组集合存在至少一个公共编号,则它们应当被视作同一簇,需合并为一个更大的集合。
请你将所有集合按上述规则进行传递式合并与去重,并按指定顺序输出。
编号:仅由数字字符构成(如“1”“23”“0005”)。
排序规则
- 行内排序:将一个合并后的集合中的编号按字典序(字符串比较)升序排列后输出为一行。
- 行间排序:将所有行作为“编号有序序列”,按字典序(逐个编号从左到右比较)升序排列后输出。
输入描述:
第1行:整数 N,表示有 N 行匹配结果。
接下来的 N 行:每行是若干个用空格分隔的数字字符串。
输出描述:
输出 M 行(M ≤ N)。每一行是一组经传递式合并与去重后的编号序列,满足“行内字典序、行间字典序”的排序要求。
解题思路
本题的核心是处理集合的传递性合并,这是一个经典的**并查集(Disjoint Set Union, DSU)**应用场景。解决步骤如下:
1. 元素映射
并查集是基于整数索引的数据结构,而本题的元素是字符串。因此,我们需要建立一个映射关系:
map<string, int> id_map: 将每个唯一的字符串编号映射到一个从 0 开始的整数 ID。vector<string> name_map: 存储整数 ID 对应的原始字符串,用于最后的结果输出。
我们首先遍历所有输入,构建这两个映射表。
2. 并查集合并
- 初始化一个大小为唯一编号总数的并查集。
- 再次遍历每一行输入。对于行内的所有编号,将它们
union到同一个集合中。具体操作是,将行内第一个编号作为代表,后续所有编号都与第一个编号进行合并。
3. 分组与去重
合并完成后,并查集中的每个连通分量就代表一个最终的簇。
- 创建一个
map<int, set<string>> groups,键是并查集中每个集合的根节点 ID,值是一个set,用于存放该簇的所有成员字符串。使用set可以自动处理输入中可能存在的重复编号。 - 遍历所有唯一的字符串编号,通过
find操作找到其根节点,然后将字符串加入对应根节点的set中。
4. 排序与输出
- 行内排序:将
groups中的每个set转换成vector。由于set本身是有序的,但其排序规则可能与题目的字符串字典序不同(例如 C++ 的std::set),我们需要对vector进行一次字符串字典序排序。 - 行间排序:将所有排序好的
vector放入一个vector<vector<string>>中,然后对这个外层vector进行排序。大多数语言的标准库对序列的排序默认就是字典序比较,可以直接使用。 - 最后,按排序后的顺序输出结果。
代码
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <map>
#include <set>
#include <algorithm>
#include <numeric>
using namespace std;
struct DSU {
vector<int> parent;
DSU(int n) {
parent.resize(n);
iota(parent.begin(), parent.end(), 0);
}
int find(int i) {
if (parent[i] == i)
return i;
return parent[i] = find(parent[i]);
}
void unite(int i, int j) {
int root_i = find(i);
int root_j = find(j);
if (root_i != root_j) {
parent[root_i] = root_j;
}
}
};
// 字符串字典序比较
bool string_compare(const string& a, const string& b) {
return a < b;
}
// 行间字典序比较
bool vector_compare(const vector<string>& a, const vector<string>& b) {
return a < b;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL);
int n;
string line;
getline(cin, line);
n = stoi(line);
vector<vector<string>> all_lines;
map<string, int> id_map;
int next_id = 0;
for (int i = 0; i < n; ++i) {
getline(cin, line);
stringstream ss(line);
string id;
vector<string> current_line;
while (ss >> id) {
current_line.push_back(id);
if (id_map.find(id) == id_map.end()) {
id_map[id] = next_id++;
}
}
all_lines.push_back(current_line);
}
if (next_id == 0) {
return 0;
}
vector<string> name_map(next_id);
for(auto const& [key, val] : id_map){
name_map[val] = key;
}
DSU dsu(next_id);
for (const auto& line_vec : all_lines) {
if (line_vec.size() > 1) {
int first_id = id_map[line_vec[0]];
for (size_t i = 1; i < line_vec.size(); ++i) {
dsu.unite(first_id, id_map[line_vec[i]]);
}
}
}
map<int, vector<string>> groups;
for (int i = 0; i < next_id; ++i) {
groups[dsu.find(i)].push_back(name_map[i]);
}
vector<vector<string>> result;
for (auto const& [root, members] : groups) {
vector<string> sorted_members = members;
sort(sorted_members.begin(), sorted_members.end(), string_compare);
result.push_back(sorted_members);
}
sort(result.begin(), result.end(), vector_compare);
for (const auto& group : result) {
for (size_t i = 0; i < group.size(); ++i) {
cout << group[i] << (i == group.size() - 1 ? "" : " ");
}
cout << "\n";
}
return 0;
}
import java.util.*;
class DSU {
private int[] parent;
public DSU(int n) {
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
public int find(int i) {
if (parent[i] == i) {
return i;
}
return parent[i] = find(parent[i]);
}
public void unite(int i, int j) {
int rootI = find(i);
int rootJ = find(j);
if (rootI != rootJ) {
parent[rootI] = rootJ;
}
}
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = Integer.parseInt(sc.nextLine());
List<String[]> allLines = new ArrayList<>();
Map<String, Integer> idMap = new HashMap<>();
int nextId = 0;
for (int i = 0; i < n; i++) {
String[] ids = sc.nextLine().split(" ");
allLines.add(ids);
for (String id : ids) {
if (!idMap.containsKey(id)) {
idMap.put(id, nextId++);
}
}
}
if (nextId == 0) return;
String[] nameMap = new String[nextId];
for (Map.Entry<String, Integer> entry : idMap.entrySet()) {
nameMap[entry.getValue()] = entry.getKey();
}
DSU dsu = new DSU(nextId);
for (String[] line : allLines) {
if (line.length > 1) {
int firstId = idMap.get(line[0]);
for (int i = 1; i < line.length; i++) {
dsu.unite(firstId, idMap.get(line[i]));
}
}
}
Map<Integer, List<String>> groups = new HashMap<>();
for (int i = 0; i < nextId; i++) {
int root = dsu.find(i);
groups.putIfAbsent(root, new ArrayList<>());
groups.get(root).add(nameMap[i]);
}
List<List<String>> result = new ArrayList<>();
for (List<String> members : groups.values()) {
// 行内字典序排序
members.sort(String::compareTo);
result.add(members);
}
// 行间字典序排序
result.sort((l1, l2) -> {
int size = Math.min(l1.size(), l2.size());
for (int i = 0; i < size; i++) {
int cmp = l1.get(i).compareTo(l2.get(i));
if (cmp != 0) return cmp;
}
return Integer.compare(l1.size(), l2.size());
});
for (List<String> group : result) {
System.out.println(String.join(" ", group));
}
}
}
class DSU:
def __init__(self, n):
self.parent = list(range(n))
def find(self, i):
if self.parent[i] == i:
return i
self.parent[i] = self.find(self.parent[i])
return self.parent[i]
def union(self, i, j):
root_i = self.find(i)
root_j = self.find(j)
if root_i != root_j:
self.parent[root_i] = root_j
def solve():
n = int(input())
all_lines = []
id_map = {}
next_id = 0
for _ in range(n):
ids = input().split()
all_lines.append(ids)
for id_str in ids:
if id_str not in id_map:
id_map[id_str] = next_id
next_id += 1
if next_id == 0:
return
name_map = {v: k for k, v in id_map.items()}
dsu = DSU(next_id)
for line in all_lines:
if len(line) > 1:
first_id = id_map[line[0]]
for i in range(1, len(line)):
dsu.union(first_id, id_map[line[i]])
groups = {}
for i in range(next_id):
root = dsu.find(i)
if root not in groups:
groups[root] = []
groups[root].append(name_map[i])
result = []
for root in groups:
# 行内字典序排序
sorted_members = sorted(groups[root])
result.append(sorted_members)
# 行间字典序排序 (Python默认的列表排序就是字典序)
result.sort()
for group in result:
print(" ".join(group))
solve()
算法及复杂度
- 算法: 并查集 (Disjoint Set Union)
- 时间复杂度:
是输入中所有编号的总出现次数,
是唯一编号的总数。
- 遍历输入、构建映射表:
。
- 并查集合并:
,其中
是反阿克曼函数,近乎常数。
- 分组:
。
- 排序:对所有唯一编号进行行内和行间排序。总的排序复杂度主要由所有唯一编号决定,约为
。
- 总体复杂度由遍历和排序主导。
- 空间复杂度:
- 需要存储字符串到整数的映射、并查集数组以及最终的分组结果,所有这些都与唯一编号的数量
成正比。
- 需要存储字符串到整数的映射、并查集数组以及最终的分组结果,所有这些都与唯一编号的数量
查看6道真题和解析