Time33哈希算法

原文:http://blog.csdn.net/chen_alvin/article/details/5846714

接到任务,需要编程求两个十万级记录列表的交集。分析后,有两个解决思路:一、在数据库中建立临时表,导入数据后,用SQL语句才交集,再导出数据;二、直接内存运算求解。考虑到数据记录多,数据库运算压力大,最后决定选择第二个方法。

 

涉及到数据查找比对,首先考虑到使用HashSet。HashSet最大的好处就是实现查找时间复杂度为O(1)。使用HashSet需要解决一个重要问题:冲突问题。对比研究了网上一些字符串哈希函数,发现几乎所有的流行的HashMap都采用了DJB Hash Function,俗称“Times33”算法。Times33的算法很简单,就是不断的乘33,见下面算法原型。

  1. hash(i) = hash(i-1) * 33 + str[i]  

 

Time33在效率和随机性两方面上俱佳。对于一个Hash函数,评价其优劣的标准应为随机性,即对任意一组标本,进入Hash表每一个单元(cell)之概率的平均程度,因为这个概率越平均,数据在表中的分布就越平均,表的空间利用率就越高。

 

  1. int Time33(String str) {  
  2.     int len = str.length();  
  3.     int hash = 0;  
  4.     for (int i = 0; i < len; i++)  
  5.         // (hash << 5) + hash 相当于 hash * 33  
  6.         hash = (hash << 5) + hash + (int) str.charAt(i);  
  7.     return hash;  
  8. }  

 

Java的HashSet判断冲突,主要依靠对象的HashCode和equals方法。因此对象必须重写hashCode和equals两个方法。

以下测试构造300,0000个Item对象,放入到HashSet中,测试Time33算法。

 

  1. /** 
  2.  * Item类 
  3.  * 重写hashCode和equals方法 
  4.  */  
  5. class Item {  
  6.     public Item(String[] strs) {  
  7.         columns = strs;  
  8.     }  
  9.   
  10.     public boolean equals(Object otherObject) {  
  11.         if (this == otherObject)  
  12.             return true;  
  13.         if (otherObject == null)  
  14.             return false;  
  15.         if (getClass() != otherObject.getClass())  
  16.             return false;  
  17.         if (!(otherObject instanceof Item))  
  18.             return false;  
  19.         Item other = (Item) otherObject;  
  20.         if (this.columns.length != other.columns.length)  
  21.             return false;  
  22.         for (int i = 0; i < this.columns.length; i++) {  
  23.             if (this.columns[i] != null && !this.columns[i].equals(other.columns[i]))  
  24.                 return false;  
  25.         }  
  26.         return true;  
  27.     }  
  28.   
  29.     public int hashCode() {  
  30.         StringBuffer sb = new StringBuffer();  
  31.         for (int i = 0; i < this.columns.length; i++) {  
  32.             sb.append(this.columns[i]);  
  33.         }  
  34.         return this.Time33(sb.toString());  
  35.     }  
  36.   
  37.     private int Time33(String str) {  
  38.         int len = str.length();  
  39.         int hash = 0;  
  40.         for (int i = 0; i < len; i++)  
  41.             hash = (hash << 5) + hash + (int) str.charAt(i);  
  42.         return hash;  
  43.     }  
  44.   
  45.     private String[] columns;  
  46. }  

 

  1. HashSet<Item> hashSet = new HashSet<Item>();  
  2. long start;  
  3. long end;  
  4. String[] strs = { "Alvin""Chan""HashSet""WOW" };  
  5. String[] tmp;  
  6. int count = 0;  
  7.       
  8. start = System.currentTimeMillis();  
  9.   
  10. for (int i = 0; i < 3000000; i++) {  
  11.     tmp = new String[4];  
  12.     for (int j = 0; j < 4; j++) {  
  13.         // 用strs中随机抽取字符串,并加入随机数,生成tmp字符串数组  
  14.         tmp[j] = strs[(int) (Math.random() * 3)] + (int) (Math.random() * i);  
  15.     }  
  16.     // 加入无法插入到hashSet中,计数器加1  
  17.     if(!hashSet.add(new Item(tmp))) {  
  18.         count++;  
  19.     }  
  20. }  
  21.   
  22. end = System.currentTimeMillis();  
  23.   
  24. System.out.println("插入300,0000条记录");  
  25. System.out.println("所需时间:" + (end - start) + " ms");  
  26. System.out.println("插入个数:" + hashSet.size());  
  27. System.out.println("失败次数:" + count);  

 

测试运行了5次,结果如下:(数据量较大,建议加大JVM的内存再运行测试,否则会内存溢出)

  1. 1次  
  2. 插入300,0000条记录  
  3. 所需时间:34203 ms  
  4. 插入个数:3000000  
  5. 失败次数:0  
  6.   
  7. 2次  
  8. 插入300,0000条记录  
  9. 所需时间:33063 ms  
  10. 插入个数:3000000  
  11. 失败次数:0  
  12.   
  13. 3次  
  14. 插入300,0000条记录  
  15. 所需时间:33016 ms  
  16. 插入个数:3000000  
  17. 失败次数:0  
  18.   
  19. 4次  
  20. 插入300,0000条记录  
  21. 所需时间:33062 ms  
  22. 插入个数:3000000  
  23. 失败次数:0  
  24.   
  25. 5次  
  26. 插入300,0000条记录  
  27. 所需时间:33140 ms  
  28. 插入个数:3000000  
  29. 失败次数:0  

 

从测试结果来看,面对百万级条记录,使用了Time33作为哈希函数的HashSet能较好地解决冲突问题。


  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Anytime repair A* (ARA*) 算法是一种在有限时间内求解最优路径的启发式搜索算法。相比于传统的 A* 算法,ARA* 算法允许在有限时间内返回一个次优解,然后继续搜索以找到更优解。本文将介绍如何用 Java 实现 ARA* 算法。 首先,定义一个节点类来表示搜索树中的每个节点: ```java public class Node { public int x; public int y; public double g; public double h; public double f; public Node parent; public Node(int x, int y) { this.x = x; this.y = y; } } ``` 其中,x 和 y 表示节点在地图上的坐标,g 表示从起点到该节点的实际代价,h 表示从该节点到终点的估计代价,f = g + h 表示该节点的总代价,parent 表示该节点的父节点。 然后,定义一个 ARAStar 类来实现 ARA* 算法: ```java import java.util.*; public class ARAStar { private double weight; private double epsilon; public ARAStar(double weight, double epsilon) { this.weight = weight; this.epsilon = epsilon; } public List<Node> search(Node start, Node goal, Map<Node, List<Node>> graph, long timeLimit) { PriorityQueue<Node> open = new PriorityQueue<>(Comparator.comparingDouble(node -> node.f)); Map<Node, Double> gScore = new HashMap<>(); Map<Node, Double> fScore = new HashMap<>(); Map<Node, Node> cameFrom = new HashMap<>(); open.add(start); gScore.put(start, 0.0); fScore.put(start, weight * heuristic(start, goal)); long startTime = System.currentTimeMillis(); while (!open.isEmpty()) { if (System.currentTimeMillis() - startTime > timeLimit) { break; } Node current = open.poll(); if (current.equals(goal)) { return reconstructPath(cameFrom, current); } for (Node neighbor : graph.getOrDefault(current, Collections.emptyList())) { double tentativeGScore = gScore.get(current) + distance(current, neighbor); if (!gScore.containsKey(neighbor) || tentativeGScore < gScore.get(neighbor)) { cameFrom.put(neighbor, current); gScore.put(neighbor, tentativeGScore); fScore.put(neighbor, weight * tentativeGScore + (1 - weight) * heuristic(neighbor, goal)); if (!open.contains(neighbor)) { open.add(neighbor); } } } if (fScore.get(current) > epsilon * fScore.get(start)) { fScore.replaceAll((node, f) -> Math.max(f, gScore.get(node) + epsilon * heuristic(node, goal))); open.addAll(open.stream() .filter(node -> fScore.get(node) > epsilon * fScore.get(start)) .map(node -> { node.f = fScore.get(node); return node; }).collect(Collectors.toList())); } } return null; } private List<Node> reconstructPath(Map<Node, Node> cameFrom, Node current) { List<Node> path = new ArrayList<>(); path.add(current); while (cameFrom.containsKey(current)) { current = cameFrom.get(current); path.add(0, current); } return path; } private double distance(Node a, Node b) { return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)); } private double heuristic(Node a, Node b) { return distance(a, b); } } ``` 其中,weight 表示启发式函数的权重,epsilon 表示次优解的最大相对误差。search 方法接收起点、终点、图和时间限制,返回搜索结果。图使用邻接表表示,每个节点是一个列表,包含与其相邻的节点。 在 search 方法中,我们使用优先队列存储开放列表,使用哈希表存储 gScore、fScore 和 cameFrom。对于每个节点,我们首先计算其 f 值,然后将其加入开放列表。当开放列表为空时,搜索结束。 在每次从开放列表中取出节点时,我们检查它是否为终点。如果是,则返回重构的路径。否则,对于每个相邻节点,我们计算 tentativeGScore,并更新 gScore、fScore 和 cameFrom。如果该节点不在开放列表中,我们将其加入开放列表。 然后,我们检查当前节点的 f 值是否超过了 epsilon 倍起点的 f 值。如果是,则重新计算所有节点的 f 值,并将开放列表中 f 值超过 epsilon 倍起点的节点重新加入开放列表。 最后,我们使用 reconstructPath 方法重构路径。该方法从终点开始,沿着 cameFrom 指针一直向前,直到到达起点。然后,它将路径反转并返回。 下面是一个使用 ARAStar 类的示例: ```java public static void main(String[] args) { Node start = new Node(1, 1); Node goal = new Node(8, 8); Map<Node, List<Node>> graph = new HashMap<>(); // 添加节点及其相邻节点 // ... ARAStar araStar = new ARAStar(1.0, 0.1); List<Node> path = araStar.search(start, goal, graph, 1000); if (path != null) { for (Node node : path) { System.out.println("(" + node.x + ", " + node.y + ")"); } } } ``` 在该示例中,我们创建了起点、终点和图,并使用 ARAStar 类进行搜索。ARAStar 构造函数接收 weight 和 epsilon 参数,search 方法接收时间限制参数。如果算法能够在指定时间内找到路径,则返回路径,否则返回 null。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值