boxmoe_header_banner_img

Hello! 欢迎来到悠悠畅享网!

文章导读

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


avatar
作者 2025年10月12日 8

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 中。

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

即构数智人

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

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

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

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数据开始,然后重点阐述了如何基于特定规则(共同特征和隐私设置)构建图结构。通过将人员及其关系抽象为图的节点和边,我们能够以清晰、高效的方式实现联系人推荐功能。理解图数据结构及其在实际问题中的应用,是开发复杂推荐系统和社交网络分析工具的关键一步。



评论(已关闭)

评论已关闭

text=ZqhQzanResources