楼主: 648811966
1006 1

[其他] Jaccard相似度算法原理及Java实现 [推广有奖]

  • 0关注
  • 0粉丝

准贵宾(月)

学前班

40%

还不是VIP/贵宾

-

威望
0
论坛币
1000 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
20 点
帖子
1
精华
0
在线时间
0 小时
注册时间
2018-10-25
最后登录
2018-10-25

楼主
648811966 发表于 2025-12-12 11:35:02 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

一、算法原理

Jaccard相似度系数(Jaccard Similarity Coefficient)是一种用于评估两个集合之间相似程度的统计方法。其核心思想是通过计算两个集合交集元素数量与并集元素总数的比值来衡量相似性,公式如下:

J(A,B) = |A ∩ B| / |A ∪ B|

该系数的取值范围在0到1之间:

  • 当结果为0时,表示两个集合无任何共同元素;
  • 当结果为1时,说明两个集合完全重合。

此外,可通过 J_distance(A,B) = 1 - J(A,B) 得到Jaccard距离,用以反映集合间的差异程度。

二、应用场景

Jaccard相似度广泛应用于多个领域:

  • 文本相似度计算:基于词袋模型将文本转化为词汇集合进行比较;
  • 推荐系统:分析用户兴趣集合的重合度以实现个性化推荐;
  • 数据挖掘:用于聚类分析或异常检测中的集合对比;
  • 生物信息学:比较基因序列或功能注释集合;
  • 网络安全:识别恶意软件行为模式的相似性。

Java实现示例

1. 基础实现

基础版本使用HashSet完成集合操作,直接依据定义实现交集与并集的计算逻辑。

import java.util.HashSet;
import java.util.Set;

public class JaccardSimilarity {
    
    /**
     * 计算两个集合的Jaccard相似度
     */
    public static double calculateJaccardSimilarity(Set<?> setA, Set<?> setB) {
        if (setA == null || setB == null || setA.isEmpty() || setB.isEmpty()) {
            return 0.0;
        }
        
        // 计算交集
        Set<Object> intersection = new HashSet<>(setA);
        intersection.retainAll(setB);
        
        // 计算并集
        Set<Object> union = new HashSet<>(setA);
        union.addAll(setB);
        
        // 防止除零错误
        if (union.isEmpty()) {
            return 0.0;
        }
        
        return (double) intersection.size() / union.size();
    }
    
    /**
     * 计算Jaccard距离
     */
    public static double calculateJaccardDistance(Set<?> setA, Set<?> setB) {
        return 1 - calculateJaccardSimilarity(setA, setB);
    }
}

2. 文本相似度应用

将两段文本分词后构建成词汇集合,利用Jaccard系数判断内容相似性,适用于去重、抄袭检测等场景。

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class TextJaccardSimilarity {
    
    /**
     * 将文本转换为词集合(简单分词)
     */
    public static Set<String> textToWordSet(String text) {
        if (text == null || text.trim().isEmpty()) {
            return new HashSet<>();
        }
        
        // 转换为小写,按空格和标点分割
        String[] words = text.toLowerCase()
                           .replaceAll("[^a-zA-Z0-9\\s]", " ")
                           .split("\\s+");
        
        return new HashSet<>(Arrays.asList(words));
    }
    
    /**
     * 计算两个文本的Jaccard相似度
     */
    public static double calculateTextSimilarity(String text1, String text2) {
        Set<String> set1 = textToWordSet(text1);
        Set<String> set2 = textToWordSet(text2);
        
        return JaccardSimilarity.calculateJaccardSimilarity(set1, set2);
    }
    
    /**
     * 带n-gram的文本相似度计算
     */
    public static Set<String> getNGrams(String text, int n) {
        Set<String> ngrams = new HashSet<>();
        if (text == null || text.length() < n) {
            return ngrams;
        }
        
        for (int i = 0; i <= text.length() - n; i++) {
            ngrams.add(text.substring(i, i + n));
        }
        
        return ngrams;
    }
    
    /**
     * 使用n-gram计算文本相似度
     */
    public static double calculateNGramSimilarity(String text1, String text2, int n) {
        Set<String> ngrams1 = getNGrams(text1, n);
        Set<String> ngrams2 = getNGrams(text2, n);
        
        return JaccardSimilarity.calculateJaccardSimilarity(ngrams1, ngrams2);
    }
}

3. 推荐系统应用示例

通过比较不同用户的物品偏好集合(如浏览记录、购买历史),找出相似用户群体,进而实现协同过滤推荐。

import java.util.*;

public class UserSimilarityRecommendation {
    
    static class User {
        String userId;
        Set<String> itemSet; // 用户购买/喜欢的物品集合
        
        public User(String userId, Set<String> itemSet) {
            this.userId = userId;
            this.itemSet = itemSet;
        }
    }
    
    /**
     * 计算用户相似度矩阵
     */
    public static Map<String, Map<String, Double>> calculateUserSimilarityMatrix(
            List<User> users) {
        
        Map<String, Map<String, Double>> similarityMatrix = new HashMap<>();
        
        for (int i = 0; i < users.size(); i++) {
            User user1 = users.get(i);
            Map<String, Double> similarities = new HashMap<>();
            
            for (int j = 0; j < users.size(); j++) {
                if (i != j) {
                    User user2 = users.get(j);
                    double similarity = JaccardSimilarity.calculateJaccardSimilarity(
                        user1.itemSet, user2.itemSet);
                    similarities.put(user2.userId, similarity);
                }
            }
            
            similarityMatrix.put(user1.userId, similarities);
        }
        
        return similarityMatrix;
    }
    
    /**
     * 基于用户的协同过滤推荐
     */
    public static Set<String> recommendItems(User targetUser, 
                                           List<User> allUsers,
                                           int topK) {
        
        // 存储用户相似度
        Map<String, Double> userSimilarities = new HashMap<>();
        
        for (User user : allUsers) {
            if (!user.userId.equals(targetUser.userId)) {
                double similarity = JaccardSimilarity.calculateJaccardSimilarity(
                    targetUser.itemSet, user.itemSet);
                userSimilarities.put(user.userId, similarity);
            }
        }
        
        // 获取最相似的K个用户
        List<String> topSimilarUsers = userSimilarities.entrySet().stream()
            .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue()))
            .limit(topK)
            .map(Map.Entry::getKey)
            .toList();
        
        // 收集推荐物品
        Set<String> recommendedItems = new HashSet<>();
        for (String userId : topSimilarUsers) {
            User similarUser = allUsers.stream()
                .filter(u -> u.userId.equals(userId))
                .findFirst()
                .orElse(null);
            
            if (similarUser != null) {
                // 推荐目标用户没有的物品
                Set<String> newItems = new HashSet<>(similarUser.itemSet);
                newItems.removeAll(targetUser.itemSet);
                recommendedItems.addAll(newItems);
            }
        }
        
        return recommendedItems;
    }
}

4. 测试示例

提供一组测试数据验证算法正确性,包括边界情况(空集、全同集合)和一般情况下的输出结果校验。

public class JaccardExample {
    public static void main(String[] args) {
        // 示例1:基础集合相似度
        Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
        Set<Integer> set2 = new HashSet<>(Arrays.asList(3, 4, 5, 6, 7));
        
        double similarity = JaccardSimilarity.calculateJaccardSimilarity(set1, set2);
        System.out.println("集合相似度: " + similarity);
        System.out.println("集合距离: " + JaccardSimilarity.calculateJaccardDistance(set1, set2));
        
        // 示例2:文本相似度
        String text1 = "Java is a programming language";
        String text2 = "Python is also a programming language";
        
        double textSimilarity = TextJaccardSimilarity.calculateTextSimilarity(text1, text2);
        System.out.println("\n文本相似度: " + textSimilarity);
        
        // 示例3:n-gram相似度
        double ngramSimilarity = TextJaccardSimilarity.calculateNGramSimilarity("hello", "hallo", 2);
        System.out.println("2-gram相似度: " + ngramSimilarity);
        
        // 示例4:推荐系统
        User user1 = new User("U1", new HashSet<>(Arrays.asList("item1", "item2", "item3")));
        User user2 = new User("U2", new HashSet<>(Arrays.asList("item2", "item3", "item4")));
        User user3 = new User("U3", new HashSet<>(Arrays.asList("item1", "item3", "item5")));
        
        List<User> users = Arrays.asList(user1, user2, user3);
        Set<String> recommendations = UserSimilarityRecommendation.recommendItems(user1, users, 2);
        
        System.out.println("\n为用户" + user1.userId + "推荐: " + recommendations);
    }
}

5. 性能优化版本(处理大数据)

针对大规模数据集采用MinHash等近似算法降低计算复杂度,在保证精度的前提下提升运行效率,适合分布式环境部署。

import java.util.*;
import java.util.stream.Collectors;

public class OptimizedJaccard {
    
    /**
     * 使用MinHash近似计算Jaccard相似度(适用于大数据集)
     */
    public static class MinHashJaccard {
        private final int numHashes;
        private final int[] hashCoefficientsA;
        private final int[] hashCoefficientsB;
        private final int largePrime = 2147483647; // 大素数
        
        public MinHashJaccard(int numHashes) {
            this.numHashes = numHashes;
            this.hashCoefficientsA = new int[numHashes];
            this.hashCoefficientsB = new int[numHashes];
            
            Random rand = new Random(42); // 固定种子保证可重复性
            for (int i = 0; i < numHashes; i++) {
                hashCoefficientsA[i] = rand.nextInt(largePrime - 1) + 1;
                hashCoefficientsB[i] = rand.nextInt(largePrime);
            }
        }
        
        /**
         * 计算MinHash签名
         */
        public int[] computeMinHashSignature(Set<String> set) {
            int[] minHash = new int[numHashes];
            Arrays.fill(minHash, Integer.MAX_VALUE);
            
            for (String element : set) {
                int hash = element.hashCode();
                for (int i = 0; i < numHashes; i++) {
                    int hashValue = (hashCoefficientsA[i] * hash + hashCoefficientsB[i]) % largePrime;
                    minHash[i] = Math.min(minHash[i], hashValue);
                }
            }
            
            return minHash;
        }
        
        /**
         * 通过MinHash估计Jaccard相似度
         */
        public double estimateJaccardSimilarity(Set<String> setA, Set<String> setB) {
            int[] signatureA = computeMinHashSignature(setA);
            int[] signatureB = computeMinHashSignature(setB);
            
            int equalCount = 0;
            for (int i = 0; i < numHashes; i++) {
                if (signatureA[i] == signatureB[i]) {
                    equalCount++;
                }
            }
            
            return (double) equalCount / numHashes;
        }
    }
    
    /**
     * 使用位图表示集合(适用于元素有限的情况)
     */
    public static class BitmapJaccard {
        private final Map<String, Integer> elementIndex;
        private final BitSet bitSet;
        
        public BitmapJaccard(Set<String> allElements) {
            elementIndex = new HashMap<>();
            int index = 0;
            for (String element : allElements) {
                elementIndex.put(element, index++);
            }
            bitSet = new BitSet(elementIndex.size());
        }
        
        public BitSet toBitSet(Set<String> elements) {
            BitSet bs = new BitSet(elementIndex.size());
            for (String element : elements) {
                Integer idx = elementIndex.get(element);
                if (idx != null) {
                    bs.set(idx);
                }
            }
            return bs;
        }
        
        public double calculateSimilarity(BitSet bs1, BitSet bs2) {
            BitSet intersection = (BitSet) bs1.clone();
            intersection.and(bs2);
            
            BitSet union = (BitSet) bs1.clone();
            union.or(bs2);
            
            return (double) intersection.cardinality() / union.cardinality();
        }
    }
}

优缺点分析

优点:

  • 简单直观:基于集合运算,易于理解和实现;
  • 计算高效:时间复杂度接近线性,适合中等规模数据快速处理;
  • 不受顺序影响:适用于无序集合,无需考虑元素排列;
  • 适用于稀疏数据:在高维稀疏向量(如文本特征)中表现良好。

缺点:

  • 忽略元素频率:仅判断是否存在,不考虑词频或其他重复信息;
  • 对集合大小敏感:小集合间容易因少量重叠导致高相似度误判;
  • 不考虑元素权重:所有元素被视为同等重要,缺乏加权机制;
  • 不适用于有序数据:原始顺序信息在计算过程中丢失。

Jaccard相似度系数作为一种简洁有效的集合相似性度量方式,具备良好的通用性和可扩展性,尤其适合处理离散型集合数据,在多种实际工程问题中具有重要应用价值。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:Jaccard Java card ACCA jav

沙发
xujingjun 发表于 2026-1-8 09:18:01

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
加好友,备注ck
拉您进交流群
GMT+8, 2026-2-3 12:37