首页 > Java > java教程 > 正文

Java中构建非加权图实现联系人推荐系统

花韻仙語
发布: 2025-10-12 13:38:31
原创
586人浏览过

Java中构建非加权图实现联系人推荐系统

本教程详细阐述了如何在java中为推荐系统构建一个非加权图。内容涵盖了从csv文件加载人员和活动数据,将数据存储为对象列表,以及基于共同社区、学校或雇主来定义“密切联系人”关系并构建图结构。同时,文章强调了隐私设置在推荐逻辑中的应用,并提供了具体的代码示例和实现建议,旨在帮助开发者有效组织和处理复杂的人际关系数据。

引言:图数据结构在推荐系统中的应用

在构建基于人际关系的推荐系统时,图数据结构是一种极其强大且直观的工具。它能够有效地表示实体(如人)及其之间的复杂关系。例如,在一个联系人推荐系统中,我们可以将每个人视为图中的一个“节点”(Vertex),而他们之间的“密切联系”则可以表示为连接这些节点的“边”(Edge)。本教程将指导您如何从原始数据开始,逐步构建一个非加权图,以实现基于共同社区、学校或雇主以及隐私设置的联系人推荐功能。

数据模型设计

在处理数据之前,首先需要定义相应的数据模型来封装人员和活动信息。这将有助于我们以面向对象的方式组织数据。

Person 类

Person 类应包含所有与个人相关的信息,包括用于判断“密切联系人”的属性以及隐私设置。

public class Person {
    private String firstname;
    private String lastname;
    private String phone;
    private String email;
    private String community;
    private String school;
    private String employer;
    private String privacy; // "N" for no privacy, "Y" for privacy

    // 构造函数
    public Person(String firstname, String lastname, String phone, String email, 
                  String community, String school, String employer, String privacy) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.phone = phone;
        this.email = email;
        this.community = community;
        this.school = school;
        this.employer = employer;
        this.privacy = privacy;
    }

    // Getters
    public String getFirstname() { return firstname; }
    public String getLastname() { return lastname; }
    public String getCommunity() { return community; }
    public String getSchool() { return school; }
    public String getEmployer() { return employer; }
    public String getPrivacy() { return privacy; }
    public boolean hasPrivacyEnabled() { return "Y".equalsIgnoreCase(privacy); }

    // 为了在Map或Set中正确使用Person对象,需要重写equals和hashCode方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        // 假设firstname和lastname的组合唯一标识一个人
        return firstname.equalsIgnoreCase(person.firstname) &&
               lastname.equalsIgnoreCase(person.lastname);
    }

    @Override
    public int hashCode() {
        return (firstname.toLowerCase() + lastname.toLowerCase()).hashCode();
    }

    @Override
    public String toString() {
        return firstname + " " + lastname + " (Community: " + community + ", School: " + school + ", Employer: " + employer + ", Privacy: " + privacy + ")";
    }

    // Setters (根据需要添加)
}
登录后复制

Activity 类

Activity 类用于存储个人的活动信息。

立即学习Java免费学习笔记(深入)”;

public class Activity {
    private String firstname;
    private String lastname;
    private String activity;

    // 构造函数
    public Activity(String firstname, String lastname, String activity) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.activity = activity;
    }

    // Getters
    public String getFirstname() { return firstname; }
    public String getLastname() { return lastname; }
    public String getActivity() { return activity; }

    @Override
    public String toString() {
        return firstname + " " + lastname + ": " + activity;
    }

    // Setters (根据需要添加)
}
登录后复制

数据加载与持久化

从CSV文件读取数据是构建系统的第一步。关键在于将读取到的每一行数据解析成对应的对象,并将其存储在一个集合中,以便后续处理。

InfoReader 类改进

原始代码中读取数据后并未存储对象,导致数据丢失。以下是改进后的 InfoReader 类,它将解析后的 Person 和 Activity 对象分别存储到 ArrayList 中。

即构数智人
即构数智人

即构数智人是由即构科技推出的AI虚拟数字人视频创作平台,支持数字人形象定制、短视频创作、数字人直播等。

即构数智人 36
查看详情 即构数智人
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class InfoReader {

    private List<Person> persons = new ArrayList<>();
    private List<Activity> activities = new ArrayList<>();

    public void readInfo() {
        // 读取人员数据
        try {
            String fileLocation = "SamplefilePersons2022Oct31text.csv"; // 假设文件在项目根目录或指定路径
            File personListFile = new File(fileLocation);
            Scanner personScanner = new Scanner(personListFile);

            while (personScanner.hasNextLine()) {
                String nextLine = personScanner.nextLine();
                String[] personComponents = nextLine.split(",");

                // 确保数据完整性,防止数组越界
                if (personComponents.length >= 8) {
                    String firstname = personComponents[0].trim();
                    String lastname = personComponents[1].trim();
                    String phone = personComponents[2].trim();
                    String email = personComponents[3].trim();
                    String community = personComponents[4].trim();
                    String school = personComponents[5].trim();
                    String employer = personComponents[6].trim();
                    String privacy = personComponents[7].trim();

                    Person newPerson = new Person(firstname, lastname, phone, email, 
                                                  community, school, employer, privacy);
                    persons.add(newPerson); // 存储Person对象
                } else {
                    System.err.println("Warning: Malformed person data line skipped: " + nextLine);
                }
            }
            personScanner.close();
        } catch (FileNotFoundException e) {
            System.err.println("Person file not found: " + e.getMessage());
            throw new RuntimeException("Error reading person data.", e);
        }

        // 读取活动数据
        try {
            String fileLocation = "SamplefileActivities2022Oct31text.csv"; // 假设文件在项目根目录或指定路径
            File activityListFile = new File(fileLocation);
            Scanner activityScanner = new Scanner(activityListFile);

            while (activityScanner.hasNextLine()) {
                String nextLine = activityScanner.nextLine();
                String[] activityComponents = nextLine.split(",");

                // 确保数据完整性
                if (activityComponents.length >= 3) {
                    String firstname = activityComponents[0].trim();
                    String lastname = activityComponents[1].trim();
                    String activity = activityComponents[2].trim();

                    Activity newActivity = new Activity(firstname, lastname, activity);
                    activities.add(newActivity); // 存储Activity对象
                } else {
                    System.err.println("Warning: Malformed activity data line skipped: " + nextLine);
                }
            }
            activityScanner.close();
        } catch (FileNotFoundException e) {
            System.err.println("Activity file not found: " + e.getMessage());
            throw new RuntimeException("Error reading activity data.", e);
        }
    }

    public List<Person> getPersons() {
        return persons;
    }

    public List<Activity> getActivities() {
        return activities;
    }
}
登录后复制

注意事项:

  • 文件路径:示例中的文件路径是简化的,实际应用中应根据您的文件存放位置进行调整。
  • 错误处理:增加了对数据行完整性的基本检查 (personComponents.length >= 8),以避免潜在的 ArrayIndexOutOfBoundsException。
  • 资源关闭:确保 Scanner 对象在读取完毕后被关闭,以释放系统资源。

图的表示与构建

有了加载并存储的 Person 对象列表,我们现在可以着手构建图了。对于非加权图,邻接列表(Adjacency List)是一种非常高效且常用的表示方法,尤其适用于稀疏图(即边相对较少的图)。

邻接列表表示法

我们将使用 Map<Person, Set<Person>> 来表示图。其中,Map的键是图中的一个 Person 节点,而值是一个 Set,包含与该键 Person 节点有“密切联系”的所有其他 Person 节点。使用 Set 可以自动处理重复的联系,确保每个联系人只出现一次。

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class RecommendationGraph {

    private Map<Person, Set<Person>> adjacencyList;

    public RecommendationGraph() {
        this.adjacencyList = new HashMap<>();
    }

    /**
     * 构建图,根据“密切联系人”规则和隐私设置建立边。
     * 密切联系人定义:共享相同的社区、学校或雇主。
     * 隐私规则:如果某人设置了隐私,则不向其发送推荐(即不建立指向TA的边)。
     * @param persons 所有人员列表
     */
    public void buildGraph(List<Person> persons) {
        // 确保所有人都作为节点存在于图中,即使他们目前没有联系人
        for (Person person : persons) {
            adjacencyList.putIfAbsent(person, new HashSet<>());
        }

        // 遍历所有人员,寻找密切联系人
        for (int i = 0; i < persons.size(); i++) {
            Person personA = persons.get(i);

            for (int j = i + 1; j < persons.size(); j++) { // 避免重复比较和自连接
                Person personB = persons.get(j);

                // 检查是否为密切联系人
                boolean isCloseContact = 
                    (personA.getCommunity() != null && !personA.getCommunity().isEmpty() && 
                     personA.getCommunity().equalsIgnoreCase(personB.getCommunity())) ||
                    (personA.getSchool() != null && !personA.getSchool().isEmpty() && 
                     personA.getSchool().equalsIgnoreCase(personB.getSchool())) ||
                    (personA.getEmployer() != null && !personA.getEmployer().isEmpty() && 
                     personA.getEmployer().equalsIgnoreCase(personB.getEmployer()));

                if (isCloseContact) {
                    // 如果PersonB没有启用隐私,则PersonA可以向PersonB推荐
                    if (!personB.hasPrivacyEnabled()) {
                        adjacencyList.get(personA).add(personB);
                    }
                    // 如果PersonA没有启用隐私,则PersonB可以向PersonA推荐
                    if (!personA.hasPrivacyEnabled()) {
                        adjacencyList.get(personB).add(personA);
                    }
                }
            }
        }
    }

    /**
     * 获取指定人员的推荐联系人列表。
     * @param person 要获取推荐的人员
     * @return 该人员的密切联系人集合
     */
    public Set<Person> getRecommendationsFor(Person person) {
        return adjacencyList.getOrDefault(person, new HashSet<>());
    }

    // 打印图结构(可选)
    public void printGraph() {
        for (Map.Entry<Person, Set<Person>> entry : adjacencyList.entrySet()) {
            System.out.println(entry.getKey().getFirstname() + " " + entry.getKey().getLastname() + " -> " + entry.getValue().size() + " contacts:");
            for (Person contact : entry.getValue()) {
                System.out.println("  - " + contact.getFirstname() + " " + contact.getLastname());
            }
        }
    }
}
登录后复制

图构建逻辑详解

  1. 初始化节点: 首先,遍历所有 Person 对象,将每个人都作为图中的一个节点添加到邻接列表中。即使他们目前没有联系人,也确保他们在图中占有一席之地。
  2. 寻找密切联系人: 使用嵌套循环比较每对不同的人 (personA 和 personB)。
    • 密切联系条件: 如果 personA 和 personB 共享相同的社区、学校或雇主,则认为他们是“密切联系人”。这里使用了 equalsIgnoreCase 和非空检查,以提高健壮性。
    • 隐私处理: 推荐系统的隐私规则是“如果一个人请求了隐私,则不向该人发送推荐”。这意味着,如果 personB 启用了隐私,那么即使 personA 和 personB 是密切联系人,personA 也不能向 personB 推荐(即不能在 personA 的邻接列表中添加 personB)。反之亦然。因此,在建立边时,需要分别检查两个方向的隐私设置。如果 personA 没有隐私,personB 可以向 personA 推荐;如果 personB 没有隐私,personA 可以向 personB 推荐。

推荐系统逻辑示例

一旦图构建完成,获取某个人的推荐联系人就变得非常简单,只需查询其在邻接列表中的邻居即可。

public class RecommendationSystemApp {
    public static void main(String[] args) {
        // 1. 读取数据
        InfoReader reader = new InfoReader();
        try {
            reader.readInfo();
        } catch (RuntimeException e) {
            System.err.println("Failed to read data: " + e.getMessage());
            return;
        }
        List<Person> allPersons = reader.getPersons();
        // List<Activity> allActivities = reader.getActivities(); // 活动数据当前未用于图构建,但可以用于其他推荐维度

        // 2. 构建推荐图
        RecommendationGraph graph = new RecommendationGraph();
        graph.buildGraph(allPersons);

        // 3. 获取并打印推荐
        System.out.println("--- Recommendation Results ---");
        for (Person person : allPersons) {
            System.out.println("\nRecommendations for " + person.getFirstname() + " " + person.getLastname() + ":");
            Set<Person> recommendations = graph.getRecommendationsFor(person);
            if (recommendations.isEmpty()) {
                System.out.println("  No close contacts found or all contacts have privacy enabled for this person.");
            } else {
                for (Person recommendedPerson : recommendations) {
                    System.out.println("  - " + recommendedPerson.getFirstname() + " " + recommendedPerson.getLastname());
                }
            }
        }

        // 示例:查找特定人物的推荐
        // 假设我们要找 "Rajay Mccalla" 的推荐
        Person targetPerson = null;
        for(Person p : allPersons) {
            if ("Rajay".equalsIgnoreCase(p.getFirstname()) && "Mccalla".equalsIgnoreCase(p.getLastname())) {
                targetPerson = p;
                break;
            }
        }

        if (targetPerson != null) {
            System.out.println("\n--- Specific Recommendation for " + targetPerson.getFirstname() + " " + targetPerson.getLastname() + " ---");
            Set<Person> specificRecs = graph.getRecommendationsFor(targetPerson);
            if (specificRecs.isEmpty()) {
                System.out.println("  No specific recommendations found.");
            } else {
                for (Person rec : specificRecs) {
                    System.out.println("  - " + rec.getFirstname() + " " + rec.getLastname());
                }
            }
        } else {
            System.out.println("\nTarget person 'Rajay Mccalla' not found.");
        }
    }
}
登录后复制

注意事项与优化

  1. 性能考量:
    • 图构建效率: 当前的图构建方法使用了嵌套循环(O(N^2)),对于小规模数据集是可接受的。但如果人员数量 N 非常大(例如,数百万),这种方法会变得非常慢。
    • 优化方法: 可以通过预处理数据来优化。例如,创建 Map<String, List<Person>> 来按社区、学校或雇主分组人员。这样,在寻找密切联系人时,只需比较同一组内的人员,而不是所有人员。
      // 优化思路示例:按社区分组
      Map<String, List<Person>> personsByCommunity = new HashMap<>();
      for (Person p : persons) {
          if (p.getCommunity() != null && !p.getCommunity().isEmpty()) {
              personsByCommunity.computeIfAbsent(p.getCommunity().toLowerCase(), k -> new ArrayList<>()).add(p);
          }
      }
      // 然后只在这些分组内部寻找联系人
      登录后复制
  2. 健壮性:
    • 文件路径: 确保文件路径是正确的,并且文件可读。
    • 数据清洗 CSV文件中的数据可能不规范,例如有额外的空格、缺失值或不一致的大小写。在解析时使用 .trim() 和 equalsIgnoreCase() 是良好的实践。对于缺失值,应有明确的处理策略(例如,跳过该属性的比较,或者将缺失值视为不匹配)。
    • 异常处理: 对 FileNotFoundException 和 ArrayIndexOutOfBoundsException 等进行适当处理。
  3. 扩展性:
    • 加权图: 如果“密切联系”有不同强度(例如,共享社区比共享学校更重要),可以引入边的权重,将 Set<Person> 改为 Map<Person, Integer> 或自定义边对象。
    • 专业图库: 对于更复杂的图算法(如最短路径、社区发现、中心性分析等),可以考虑使用成熟的图处理库,例如 JGraphTApache TinkerPop。这些库提供了丰富的图数据结构和算法实现。
  4. 内存管理: 对于非常大的数据集,ArrayList 和 HashMap 可能会占用大量内存。在极端情况下,可能需要考虑使用外部存储或分布式图数据库。

总结

本教程详细展示了如何从零开始,在Java中构建一个用于推荐系统的非加权图。我们从定义数据模型、安全地加载和存储CSV数据开始,然后重点阐述了如何基于特定规则(共同特征和隐私设置)构建图结构。通过将人员及其关系抽象为图的节点和边,我们能够以清晰、高效的方式实现联系人推荐功能。理解图数据结构及其在实际问题中的应用,是开发复杂推荐系统和社交网络分析工具的关键一步。

以上就是Java中构建非加权图实现联系人推荐系统的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号