You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求适用于百万级姓名数据集的相似姓名聚类算法方案

百万级相似姓名分组优化方案

原有的双重循环方案在百万级数据下完全不可行——时间复杂度为O(n²),数据量上升后计算量会直接爆炸。下面给出几个能处理大规模数据集的优化思路和实现方案,同时满足「优先归入匹配度最高的组,允许归入多组」的要求:


一、预过滤+N-Gram倒排索引(易落地、性价比高)

核心思路是避免每个姓名与所有组名全量比较,先通过N-Gram快速缩小候选范围,再对候选集计算编辑距离做精细匹配。

实现步骤:

  1. 构建N-Gram倒排索引:对每个姓名生成所有N元字符组(比如N=2时,John生成jo、oh、hn),用字典存储每个N-Gram对应的姓名列表。
  2. 候选筛选:处理新姓名时,提取其N-Gram,从索引中找到共享足够多N-Gram的已分组姓名,作为候选组名。
  3. 精细匹配:计算候选组名与当前姓名的编辑距离,优先加入距离最小的组,同时可加入其他符合阈值的组。

代码示例(Python):

import Levenshtein
from collections import defaultdict

def build_ngram_index(names, n=2):
    ngram_index = defaultdict(list)
    for name in names:
        # 生成N-Gram,保留原始姓名用于后续匹配
        grams = [name[i:i+n] for i in range(len(name)-n+1)] if len(name)>=n else [name]
        for gram in grams:
            ngram_index[gram].append(name)
    return ngram_index

def group_similar_names_large_scale(names, threshold=2, n=2, min_shared_grams=1):
    groups = {}
    ngram_index = build_ngram_index(names)
    
    for name in names:
        # 从N-Gram索引中筛选候选组名
        candidate_group_names = set()
        grams = [name[i:i+n] for i in range(len(name)-n+1)] if len(name)>=n else [name]
        for gram in grams:
            for candidate in ngram_index.get(gram, []):
                if candidate in groups:
                    candidate_group_names.add(candidate)
        
        # 计算编辑距离并排序,优先匹配度最高的组
        valid_pairs = []
        for group_name in candidate_group_names:
            dist = Levenshtein.distance(name, group_name)
            if dist <= threshold:
                valid_pairs.append((dist, group_name))
        
        if valid_pairs:
            # 按距离升序,先加入匹配度最高的组
            valid_pairs.sort()
            min_dist, best_group = valid_pairs[0]
            groups[best_group].append(name)
            # 加入其他符合阈值的组(如果需要)
            for dist, group_name in valid_pairs[1:]:
                groups[group_name].append(name)
        else:
            groups[name] = [name]
    
    return groups.values()

二、向量近似搜索+聚类(超大规模场景适用)

如果数据量达到千万级,预过滤仍不够高效,可以将姓名转换为向量,用近似近邻搜索(ANN)工具快速定位相似目标,再完成分组。

实现步骤:

  1. 姓名向量化:用字符词袋模型(统计每个字符出现次数)或N-Gram向量将姓名转为数值向量。
  2. 构建ANN索引:用FAISS、Annoy等库构建向量索引,实现O(log n)级别的相似性搜索。
  3. 匹配分组:找到相似候选后,用编辑距离确认,优先加入匹配度最高的组,支持多组加入。

代码示例(Python+FAISS):

import Levenshtein
import faiss
import numpy as np
from collections import defaultdict

def name_to_vector(name, char_set):
    # 字符词袋向量:每个字符对应一个维度,值为出现次数
    char_to_idx = {c:i for i,c in enumerate(char_set)}
    vec = np.zeros(len(char_set), dtype=np.float32)
    for c in name.lower():
        if c in char_to_idx:
            vec[char_to_idx[c]] += 1
    return vec

def group_similar_names_faiss(names, threshold=2, top_k=5):
    # 收集所有出现过的字符
    all_chars = set(''.join(names).lower())
    char_list = list(all_chars)
    
    # 转换所有姓名为向量
    vectors = np.array([name_to_vector(name, char_list) for name in names], dtype=np.float32)
    
    # 构建FAISS索引
    index = faiss.IndexFlatL2(len(char_list))
    index.add(vectors)
    
    groups = {name:[name] for name in names}
    name_to_idx = {name:i for i,name in enumerate(names)}
    
    for name in names:
        vec = name_to_vector(name, char_list).reshape(1, -1)
        # 搜索Top-K相似姓名(跳过自身)
        distances, indices = index.search(vec, top_k+1)
        
        best_dist = float('inf')
        best_group = None
        valid_candidates = []
        
        for idx, dist in zip(indices[0], distances[0]):
            candidate_name = names[idx]
            if candidate_name == name:
                continue
            edit_dist = Levenshtein.distance(name, candidate_name)
            if edit_dist <= threshold:
                valid_candidates.append((edit_dist, candidate_name))
                if edit_dist < best_dist:
                    best_dist = edit_dist
                    best_group = candidate_name
        
        if best_group:
            groups[best_group].append(name)
            # 加入其他符合条件的组
            for edit_dist, group_name in valid_candidates:
                if group_name != best_group:
                    groups[group_name].append(name)
    
    # 去重组内重复项
    for group_key in groups:
        groups[group_key] = list(set(groups[group_key]))
    
    return groups.values()

三、规则预分组+组内精细匹配(轻量化方案)

如果不需要复杂算法,可先通过规则将姓名分成大组,再在组内做编辑距离比较,大幅减少计算量:

  • 按姓名长度+首2字符分组:比如John(长度4,首2字符jo)分到jo_4组,直接排除长度差超过阈值的姓名
  • 提取核心姓名:去掉非字母字符并转小写,先按核心姓名分组,再处理核心相似的姓名(如Jon和John)

代码示例:

import Levenshtein
from collections import defaultdict

def get_pre_group_key(name):
    # 预分组key:首2个小写字母 + 姓名长度
    alpha_part = ''.join([c.lower() for c in name if c.isalpha()])
    prefix = alpha_part[:2] if len(alpha_part)>=2 else alpha_part
    return f"{prefix}_{len(name)}"

def group_similar_names_rule_based(names, threshold=2):
    # 规则预分组
    pre_groups = defaultdict(list)
    for name in names:
        key = get_pre_group_key(name)
        pre_groups[key].append(name)
    
    final_groups = []
    # 预分组内做精细匹配
    for pre_group in pre_groups.values():
        groups = {}
        for name in pre_group:
            best_dist = float('inf')
            best_group_name = None
            valid_pairs = []
            for group_name in groups:
                dist = Levenshtein.distance(name, group_name)
                if dist <= threshold:
                    valid_pairs.append((dist, group_name))
                    if dist < best_dist:
                        best_dist = dist
                        best_group_name = group_name
            if best_group_name:
                groups[best_group_name].append(name)
                for dist, group_name in valid_pairs:
                    if group_name != best_group_name:
                        groups[group_name].append(name)
            else:
                groups[name] = [name]
        final_groups.extend(groups.values())
    
    return final_groups

内容的提问来源于stack exchange,提问作者K. Claesson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 07:35:19