写点什么

20 | 散列表(下):为什么散列表和链表经常会一起使用

作者:鲁米
  • 2023-12-09
    北京
  • 本文字数:3075 字

    阅读完需:约 10 分钟

20 | 散列表(下):为什么散列表和链表经常会一起使用

今天讲的几个散列表和链表结合使用的例子里,我们用的都是双向链表。如果把双向链表改成单链表,还能否正常工作呢?为什么呢?

使用双向链表:
  1. 正常工作: 散列表使用双向链表可以正常工作。

  2. 插入和删除操作: 双向链表支持在链表中的任意位置快速插入和删除操作,这在处理冲突时可能是有利的。

  3. 遍历方向: 双向链表允许以两个方向遍历链表,这在某些情况下可能是有用的。

使用单链表:
  1. 正常工作: 散列表使用单链表同样可以正常工作。

  2. 空间效率: 单链表相对于双向链表占用更少的空间,因为不需要额外的指向前一个节点的引用。

  3. 简化实现: 如果在特定情况下不需要双向遍历链表,使用单链表可能会简化实现。

选择考虑的因素:
  1. 内存占用: 如果内存占用是关键因素,可以考虑使用单链表。

  2. 操作复杂性: 如果需要在链表中的任意位置进行频繁的插入和删除操作,双向链表可能更合适。

  3. 需求和设计选择: 具体的需求和设计选择可能会影响选择使用单链表还是双向链表。


假设猎聘网有 10 万名猎头,每个猎头都可以通过做任务(比如发布职位)来积累积分,然后通过积分来下载简历。假设你是猎聘网的一名工程师,如何在内存中存储这 10 万个猎头 ID 和积分信息,让它能够支持这样几个操作:


  • 根据猎头的 ID 快速查找、删除、更新这个猎头的积分信息;

  • 查找积分在某个区间的猎头 ID 列表;

  • 查找按照积分从小到大排名在第 x 位到第 y 位之间的猎头 ID 列表。


源码


为了支持这样的操作,你可以考虑使用一种数据结构,例如跳表(Skip List)或有序的平衡树,来存储猎头的 ID 和积分信息。这样的数据结构能够保持有序性,并且对于查找、删除、更新等操作具有较好的性能。


package com.controller.bootweb.demo.dsa;
import java.util.*;
class Headhunter { String id; int points;
public Headhunter(String id, int points) { this.id = id; this.points = points; }}
public class HeadhunterSystem { private TreeMap<Integer, List<String>> pointsMap; // 通过积分建立索引 private HashMap<String, Headhunter> headhuntersMap; // 通过ID快速查找猎头信息
public HeadhunterSystem() { pointsMap = new TreeMap<>(); headhuntersMap = new HashMap<>(); }
// 添加猎头信息 public void addHeadhunter(String id, int points) { Headhunter headhunter = new Headhunter(id, points);
// 更新积分Map pointsMap.computeIfAbsent(points, k -> new ArrayList<>()).add(id);
// 更新猎头Map headhuntersMap.put(id, headhunter); }
// 删除猎头信息 public void removeHeadhunter(String id) { Headhunter headhunter = headhuntersMap.get(id); if (headhunter != null) { // 从积分Map中移除 pointsMap.get(headhunter.points).remove(id);
// 从猎头Map中移除 headhuntersMap.remove(id); } }
// 更新猎头积分 public void updatePoints(String id, int newPoints) { removeHeadhunter(id); // 先移除再重新添加 addHeadhunter(id, newPoints); }
// 查找积分在某个区间的猎头ID列表 public List<String> findHeadhuntersInPointsRange(int start, int end) { List<String> result = new ArrayList<>();
// 使用 TreeMap 的 subMap 方法找到积分区间的子集 NavigableMap<Integer, List<String>> subMap = pointsMap.subMap(start, true, end, true);
// 合并子集中的所有ID for (List<String> ids : subMap.values()) { result.addAll(ids); }
return result; }
// 查找按照积分从小到大排名在第x位到第y位之间的猎头ID列表 public List<String> findHeadhuntersByRank(int x, int y) { List<String> result = new ArrayList<>();
// 遍历积分Map,记录排名 int currentRank = 1; for (List<String> ids : pointsMap.values()) { for (String id : ids) { if (currentRank >= x && currentRank <= y) { result.add(id); } currentRank++; } }
return result; }
public List<String> findHeadhuntersByRankOptimized(int x, int y) { List<String> result = new ArrayList<>(); int currentRank = 1;
for (List<String> ids : pointsMap.values()) { for (String id : ids) { if (currentRank > y) { // 已经超过排名范围,结束遍历 return result; }
if (currentRank >= x) { // 在排名范围内,添加到结果列表 result.add(id); }
currentRank++; } }
return result; }

// 根据ID查找猎头信息 public Headhunter findHeadhunterById(String id) { return headhuntersMap.get(id); }
// 获取所有猎头ID public Iterable<String> getAllHeadhunters() { return headhuntersMap.keySet(); }
// 获取所有积分信息 public Iterable<Integer> getAllPoints() { return pointsMap.keySet(); }}
复制代码


package com.controller.bootweb.demo.dsa;
import java.util.List;
public class TestHeadhunterSystem { public static void main(String[] args) { HeadhunterSystem headhunterSystem = new HeadhunterSystem();
// 添加猎头信息 headhunterSystem.addHeadhunter("hunter1", 100); headhunterSystem.addHeadhunter("hunter2", 150); headhunterSystem.addHeadhunter("hunter3", 120); headhunterSystem.addHeadhunter("hunter4", 180); headhunterSystem.addHeadhunter("hunter5", 90);
// 打印所有猎头ID和积分 System.out.println("All Headhunters:"); for (String id : headhunterSystem.getAllHeadhunters()) { System.out.println("ID: " + id + ", Points: " + headhunterSystem.findHeadhunterById(id).points); }
// 查找积分在某个区间的猎头ID列表 int startRange = 100; int endRange = 150; List<String> headhuntersInRange = headhunterSystem.findHeadhuntersInPointsRange(startRange, endRange); System.out.println("\nHeadhunters in Points Range (" + startRange + " to " + endRange + "):"); System.out.println(headhuntersInRange);
// 查找按照积分从小到大排名在第x位到第y位之间的猎头ID列表 int rankX = 2; int rankY = 4; List<String> headhuntersByRank = headhunterSystem.findHeadhuntersByRankOptimized(rankX, rankY); System.out.println("\nHeadhunters by Rank (" + rankX + " to " + rankY + "):"); System.out.println(headhuntersByRank); }}
复制代码


用户头像

鲁米

关注

生活黑客35 2019-06-11 加入

起点不重要,迭代很重要,就需要保持充分的开放和积累;而信息越充分,结果越可靠,又要求随时调整、不断逼近真相。

评论

发布
暂无评论
20 | 散列表(下):为什么散列表和链表经常会一起使用_鲁米_InfoQ写作社区