持续学习&持续更新中…
【恋上数据结构与算法 第一季】哈希表(散列表)
- TreeMap分析
- 需求
- 哈希表
- table数组
- 哈希冲突
- JDK1.8解决哈希冲突的方法
- 哈希函数
- 如何生成key的哈希值(hashCode)
- 如何生成key的hashCode
- Integer和Float
- Long和Double
- 字符串
- 自定义对象
- pub与findNode
- 简化版
- 优化版
- 注意点
- 扩容
- 装填因子
- 扩容后index
- HashMap的实现
- TreeMap vs HashMap
- linkedHashMap
- 实现原理
- 删除注意点
- 更换节点的连接位置
- 注意点
- 实现linkedHashMap
- 参考
HashMap中的table数组是一个Node
index = hash(key)
如何生成key的哈希值(hashCode) 如何生成key的hashCode已知:Java中的hashCode返回值必须为int型
Integer和Float Long和Double 字符串公式:之前的hashCode值 * n + 当前的hashCode值
自定义对象- 实现hashCode是为了自定义哈希函数如何生成哈希表的索引
- 实现equals是为了自定义节点比较时(索引相同时,也就是哈希冲突时,会从链表or红黑树的头遍历到尾来比较节点的key是否相同)如何是相同的节点如何是不同的节点
- 当两个对象equals时,就代表这两个对象是同一个对象,既然是同一个对象,那么它们的hashCode值必须相等才对,因此hashCode和equals都必须同时实现(equals中使用到了哪些属性来比较两个对象相等,那么hashCode的值也应该使用这些属性来计算)。
- 只比较equals
- 如果不equals,直接扫描
- 性能特别差
put
private V put(Noderoot, K key, V value) { Node node = root; Node parent = root; Node result; int h1 = (key == null ? 0 : key.hashCode()); K k1 = key; int cmp; boolean searched = false; do { parent = node; int h2 = node.hashCode; K k2 = node.key; if (Objects.equals(k1, k2)) cmp = 0; else if (searched) { // 已经搜索过 cmp = 1; // 直接往右走 } else { if ( (node.left != null && (result = findNode(node.left, k1)) != null) || (node.right != null && (result = findNode(node.right, k1)) != null) ) { // 找到了 cmp = 0; node = result; } else { // 不存在,没找到 searched = true; cmp = 1; // 同样直接往右走 } } if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { V oldValue = node.value; node.key = key; node.value = value; node.hashCode = h1; return oldValue; } } while (null != node); Node newNode = new Node<>(key, value, parent); if (cmp > 0) { parent.right = newNode; } else { // cmp < 0 parent.left = newNode; } size++; afterPut(newNode); return null; }
findNode
private Node优化版findNode(Node node, K key) { int h1 = (key == null ? 0 : key.hashCode()); K k1 = key; Node result; while (node != null) { int h2 = node.hashCode; K k2 = node.key; if (Objects.equals(k1, k2)) return node; else if (node.left != null && (result = findNode(node.left, k1)) != null) { return result; } else { // 左边找不到,只能往右边找 node = node.right; } } return null; }
put
private V put(Noderoot, K key, V value) { Node node = root; Node parent = root; Node result; int h1 = (key == null ? 0 : key.hashCode()); K k1 = key; int cmp; boolean searched = false; do { parent = node; int h2 = node.hashCode; K k2 = node.key; if (h1 > h2) { cmp = 1; } else if (h1 < h2) { cmp = -1; } else if (Objects.equals(k1, k2)) cmp = 0; else if (k1 != null && k2 != null && k1.getClass() == k2.getClass() && k1 instanceof Comparable && (cmp = ((Comparable) k1).compareTo(k2)) != 0 ) { } else if (searched) { // 已经搜索过 cmp = System.identityHashCode(k1) - System.identityHashCode(k2); } else { if ( (node.left != null && (result = findNode(node.left, k1)) != null) || (node.right != null && (result = findNode(node.right, k1)) != null) ) { // 找到了 cmp = 0; node = result; } else { // 不存在,没找到 searched = true; cmp = System.identityHashCode(k1) - System.identityHashCode(k2); } } // 性能不好,应该使用searched优化 // else if ( // (node.left != null && (result = findNode(node.left, k1)) != null) || // (node.right != null && (result = findNode(node.right, k1)) != null) // ) { // cmp = 0; // node = result; // } else { // cmp = System.identityHashCode(k1) - System.identityHashCode(k2); // } if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { V oldValue = node.value; node.key = key; node.value = value; node.hashCode = h1; return oldValue; } } while (null != node); Node newNode = new Node<>(key, value, parent); if (cmp > 0) { parent.right = newNode; } else { // cmp < 0 parent.left = newNode; } size++; afterPut(newNode); return null; }
findNode
private Node注意点findNode(Node node, K key) { int h1 = (key == null ? 0 : key.hashCode()); K k1 = key; Node result; int cmp; while (node != null) { int h2 = node.hashCode; K k2 = node.key; if (h1 > h2) { node = node.right; } else if (h1 < h2) { node = node.left; } else if (Objects.equals(k1, k2)) return node; else if (k1 != null && k2 != null && k1.getClass() == k2.getClass() && k1 instanceof Comparable && (cmp = ((Comparable) k1).compareTo(k2)) != 0 ) { node = cmp > 0 ? node.right : node.left; } else if (node.left != null && (result = findNode(node.left, k1)) != null) { return result; } else { // 左边找不到,只能往右边找 node = node.right; } // 性能不好 // else if (node.right != null && (result = findNode(node.right, k1)) != null) { // return result; // } else { // return null; // } } return null; }
-
compareTo只能用来比大小,是用来比较大小是否相等的,不能用来比较对象是否完全相等(相同)
-
equals是用来判断对象是否相等(相同)的
-
应该先比较hashCode的值,根据hashCode的大小去判断往左走还是往右走,这样会排除很多节点。
-
只有当hashCode值相等的时候才有必要去比较是否equals。
- 前提:同时规范实现了hashCode和equals
- 当两个元素equals时,它们的hashCode值一定是相等的
- 当两个元素的hashCode值相等,这两个元素不一定equals
-
put和findNode时,比较的规则越完善越多,该HashMap的效率越高。
-
除了put时在走投无路的情况下根据内存地址大小决定往左还是往右走这一种情况,put和findNode的比较规则必须是对称的。
-
findNode查找元素时,当所有规则都不能判断出两个元素是否相等,也分不清是往左走还是往右走时,不能根据内存地址的大小来判断往左走还是往右走,只能去扫描。
扩容后hash(索引、数组下标)会变 ,因此不能直接将整颗红黑树移动到新的table数组,而是需要将每个节点一个一个的移动到新数组中。
HashMap的实现-
该HashMap并没有和JDK那样使用哈希表 + 链表 + 红黑树
-
而是只使用了哈希表 + 红黑树
// 支持null作为key public class HashMapTreeMap vs HashMapimplements Map { private static final int DEFAULT_CAPACITY = 1 << 4; private static final float DEFAULT_LOAD_FACTOR = 0.75f; private int size; private Node [] table; public HashMap() { table = new Node[DEFAULT_CAPACITY]; } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public void clear() { if (size == 0) return; for (int i = 0; i < table.length; i++) { table[i] = null; } size = 0; } @Override public V put(K key, V value) { resize(); int index = hash(key); Node root = table[index]; if (null == root) { root = new Node<>(key, value, null); table[index] = root; size++; afterPut(root); return null; } // 哈希冲突,向红黑树中添加元素 return put(root, key, value); } @Override public V get(K key) { final Node node = findNode(table[hash(key)], key); if (node == null) return null; return node.value; } @Override public V remove(K key) { final Node node = findNode(table[hash(key)], key); if (node == null) return null; return remove(node); } @Override public boolean containsKey(K key) { return findNode(table[hash(key)], key) != null; } @Override public boolean containsValue(V value) { if (size == 0) return false; Queue > queue = new linkedListQueue<>(); for (int i = 0; i < table.length; i++) { Node node = table[i]; if (node == null) continue; queue.enQueue(node); do { node = queue.deQueue(); if (Objects.equals(value, node.value)) return true; if (node.left != null) queue.enQueue(node.left); if (node.right != null) queue.enQueue(node.right); } while (!queue.isEmpty()); } return false; } @Override public void traversal(Visitor visitor) { if (size == 0 || visitor == null) return; Queue > queue = new linkedListQueue<>(); for (int i = 0; i < table.length; i++) { Node node = table[i]; if (node == null) continue; queue.enQueue(node); do { node = queue.deQueue(); if (visitor.visit(node.key, node.value)) return; if (node.left != null) queue.enQueue(node.left); if (node.right != null) queue.enQueue(node.right); } while (!queue.isEmpty()); } } // HashMap不需要compare函数 // private int compare(K k1, K k2, int h1, int h2) { // // 当两个对象equals时,就认定这两个对象是同一个对象,那么它们的hashCode值应该相等才对 // // 但是当两个对象的hashCode值相等时,这两个对象不一定是equals的 // int result = h1 - h2; // if (result != 0) return result; // if (Objects.equals(k1, k2)) return 0; // if (null != k1 && null != k2 && k1.getClass() == k2.getClass() && k1 instanceof Comparable) { // return ((Comparable) k1).compareTo(k2); // } // return System.identityHashCode(k1) - System.identityHashCode(k2); // } private V put(Node root, K key, V value) { Node node = root; Node parent = root; Node result; int h1 = (key == null ? 0 : key.hashCode()); K k1 = key; int cmp; boolean searched = false; do { parent = node; int h2 = node.hashCode; K k2 = node.key; if (h1 > h2) { cmp = 1; } else if (h1 < h2) { cmp = -1; } else if (Objects.equals(k1, k2)) cmp = 0; else if (k1 != null && k2 != null && k1.getClass() == k2.getClass() && k1 instanceof Comparable && (cmp = ((Comparable) k1).compareTo(k2)) != 0 ) { } else if (searched) { // 已经搜索过 cmp = System.identityHashCode(k1) - System.identityHashCode(k2); } else { if ( (node.left != null && (result = findNode(node.left, k1)) != null) || (node.right != null && (result = findNode(node.right, k1)) != null) ) { // 找到了 cmp = 0; node = result; } else { // 不存在,没找到 searched = true; cmp = System.identityHashCode(k1) - System.identityHashCode(k2); } } // 性能不好,应该使用searched优化 // else if ( // (node.left != null && (result = findNode(node.left, k1)) != null) || // (node.right != null && (result = findNode(node.right, k1)) != null) // ) { // cmp = 0; // node = result; // } else { // cmp = System.identityHashCode(k1) - System.identityHashCode(k2); // } if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { V oldValue = node.value; node.key = key; node.value = value; node.hashCode = h1; return oldValue; } } while (null != node); Node newNode = new Node<>(key, value, parent); if (cmp > 0) { parent.right = newNode; } else { // cmp < 0 parent.left = newNode; } size++; afterPut(newNode); return null; } private Node findNode(Node node, K key) { int h1 = (key == null ? 0 : key.hashCode()); K k1 = key; Node result; int cmp; while (node != null) { int h2 = node.hashCode; K k2 = node.key; if (h1 > h2) { node = node.right; } else if (h1 < h2) { node = node.left; } else if (Objects.equals(k1, k2)) return node; else if (k1 != null && k2 != null && k1.getClass() == k2.getClass() && k1 instanceof Comparable && (cmp = ((Comparable) k1).compareTo(k2)) != 0 ) { node = cmp > 0 ? node.right : node.left; } else if (node.left != null && (result = findNode(node.left, k1)) != null) { return result; } else { // 左边找不到,只能往右边找 node = node.right; } // 性能不好 // else if (node.right != null && (result = findNode(node.right, k1)) != null) { // return result; // } else { // return null; // } } return null; } // 扩容 private void resize() { if (size / ((float) table.length) <= DEFAULT_LOAD_FACTOR) return; Node [] oldTable = table; table = new Node[oldTable.length << 1]; Queue > queue = new linkedListQueue<>(); for (int i = 0; i < oldTable.length; i++) { Node root = oldTable[i]; if (root == null) continue; queue.enQueue(root); do { Node node = queue.deQueue(); if (node.left != null) queue.enQueue(node.left); if (node.right != null) queue.enQueue(node.right); move(node); } while (!queue.isEmpty()); } } // 辅助扩容 private void move(Node newNode) { newNode.color = Node.RED; newNode.parent = null; newNode.left = null; newNode.right = null; int index = hash(newNode); Node root = table[index]; if (null == root) { root = newNode; table[index] = root; afterPut(root); return; } Node node = root; Node parent = node; int h1 = newNode.hashCode; K k1 = newNode.key; int cmp; do { parent = node; int h2 = node.hashCode; K k2 = node.key; if (h1 > h2) { cmp = 1; } else if (h1 < h2) { cmp = -1; } else if (k1 != null && k2 != null && k1.getClass() == k2.getClass() && k1 instanceof Comparable && (cmp = ((Comparable) k1).compareTo(k2)) != 0 ) { } else { cmp = System.identityHashCode(k1) - System.identityHashCode(k2); } if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } } while (null != node); newNode.parent = parent; if (cmp > 0) { parent.right = newNode; } else { // cmp < 0 parent.left = newNode; } afterPut(newNode); } // 通过hash函数计算索引 private int hash(K key) { if (key == null) return 0; int hashCode = key.hashCode(); hashCode = (hashCode ^ (hashCode >>> 16)); return hashCode & (table.length - 1); } // 通过hash函数计算索引 private int hash(Node node) { int hashCode = node.hashCode; hashCode = (hashCode ^ (hashCode >>> 16)); return hashCode & (table.length - 1); } private V remove(Node node) { V oldValue = node.value; if (node.hasTwoChildren()) { Node s = successor(node); node.key = s.key; node.value = s.value; node.hashCode = s.hashCode; node = s; } int index = hash(node); Node replacement = node.left == null ? node.right : node.left; if (replacement == null) { // 叶子节点 if (node.parent == null) { table[index] = null; } else if (node.parent.left == node) { node.parent.left = null; } else { node.parent.right = null; } } else { // 度为1的节点 replacement.parent = node.parent; if (node.parent == null) { table[index] = replacement; } else if (node.parent.left == node) { node.parent.left = replacement; } else { node.parent.right = replacement; } } size--; afterRemove(node, replacement); return oldValue; } private void afterRemove(Node node, Node replacement) { if (isRed(node)) return; // 删除的是红色节点,不用做任何处理 if (isRed(replacement)) { // 用以替代的子节点是红色节点 black(replacement); return; } // 删除的是根节点 if (node.parent == null) return; Node parent = node.parent; // 获取兄弟节点 // 叶子节点删除时如果是左孩子,会执行node.parent.left = null; boolean isLeftChild = parent.left == null || node.isLeftOfParent(); Node sibling = isLeftChild ? parent.right : parent.left; if (isLeftChild) { // 被删除的叶子节点在左边,兄弟节点在右边 // 与 “被删除的叶子节点在右边,兄弟节点在左边” 是对称的 // 旋转更新方向、用左边变为用右边、用右边变为用左边 if (isRed(sibling)) { black(sibling); red(parent); rotateLeft(parent); sibling = parent.right; } if (isBlack(sibling.left) && isBlack(sibling.right)) { boolean isBlackParent = isBlack(parent); red(sibling); black(parent); if (isBlackParent) { afterRemove(parent, null); } } else { if (isBlack(sibling.right)) { rotateRight(sibling); sibling = parent.right; } color(sibling, colorOf(parent)); black(parent); black(sibling.right); rotateLeft(parent); } } else { // 被删除的叶子节点在右边,兄弟节点在左边(老师PPT上画的情况) if (isRed(sibling)) { // 判断兄弟节点是否为RED black(sibling); red(parent); rotateRight(parent); // 更新sibling sibling = parent.left; } // 以下情况,兄弟结点肯定为黑色 // 判断sibling是否至少有1个RED子节点 if (isBlack(sibling.left) && isBlack(sibling.right)) { // sibling没有任何RED子节点 boolean isBlackParent = isBlack(parent); red(sibling); black(parent); if (isBlackParent) { afterRemove(parent, null); } } else { // sibling至少有一个RED子节点 可以向兄弟借元素 if (isBlack(sibling.left)) { // 如果兄弟节点的左边是黑色,兄弟先旋转 rotateLeft(sibling); sibling = parent.left; } // 先染色,后旋转 color(sibling, colorOf(parent)); black(parent); black(sibling.left); rotateRight(parent); //先旋转,后染色 // rotateRight(parent); // color(sibling, colorOf(parent)); // black(sibling.left); // black(sibling.right); } } } // 后继节点 private Node successor(Node node) { if (node == null) return null; Node s = node.right; if (s != null) { while (s.left != null) { s = s.left; } return s; } while (node.parent != null && node != node.parent.left) { node = node.parent; } return node.parent; } private void afterPut(Node node) { Node parent = node.parent; if (parent == null) { // 添加的是根节点 或者 上溢到了根节点 black(node); return; } if (isBlack(parent)) { // parent为BLACK 不用做任何处理 4种情况 return; } Node uncle = parent.sibling(); Node grand = parent.parent; if (isRed(uncle)) { // 会上溢 4种情况 black(parent); black(uncle); red(grand); afterPut(grand); } else { // 4种情况 if (parent.isLeftOfParent()) { // L if (node.isLeftOfParent()) { // LL black(parent); red(grand); rotateRight(grand); } else { // LR black(node); red(grand); rotateLeft(parent); rotateRight(grand); } } else { // R if (node.isLeftOfParent()) { // RL black(node); red(grand); rotateRight(parent); rotateLeft(grand); } else { // RR black(parent); red(grand); rotateLeft(grand); } } } } // 右旋转 private void rotateRight(Node g) { Node p = g.left; if (p.right != null) p.right.parent = g; g.left = p.right; p.right = g; afterRotate(g, p); } // 左旋转 private void rotateLeft(Node g) { Node p = (Node ) g.right; if (p.left != null) p.left.parent = g; g.right = p.left; p.left = g; afterRotate(g, p); } private void afterRotate(Node g, Node p) { if (g.parent != null) { if (g.isLeftOfParent()) { g.parent.left = p; } else { g.parent.right = p; } } else { table[hash(g)] = p; } p.parent = g.parent; g.parent = p; } // 给节点上色(染色) private Node color(Node node, boolean color) { if (null == node) return null; final Node rbNode = node; rbNode.color = color; return rbNode; } // 将节点染为黑色 private Node black(Node node) { return color(node, Node.BLACK); } // 将节点染为黑色 private Node red(Node node) { return color(node, Node.RED); } // 判断该节点是什么颜色 private boolean colorOf(Node node) { // 红黑树中的空节点是黑色节点 return node == null ? Node.BLACK : node.color; } // 判断节点是否为红色节点 private boolean isRed(Node node) { return colorOf(node) == Node.RED; } // 判断节点是否为黑色节点 private boolean isBlack(Node node) { return colorOf(node) == Node.BLACK; } private static class Node { // 建议新添加的节点默认为RED private static final boolean RED = false; private static final boolean BLACK = true; // 建议新添加的节点默认为RED boolean color; int hashCode; K key; V value; Node left; Node right; Node parent; Node(K key, V value, Node parent) { this.hashCode = (key == null ? 0 : key.hashCode()); this.key = key; this.value = value; this.parent = parent; } boolean isLeftOfParent() { return parent != null && parent.left == this; } boolean isRightOfParent() { return parent != null && parent.right == this; } boolean hasTwoChildren() { return left != null && right != null; } boolean isLeaf() { return left == null && right == null; } // 返回兄弟节点 Node sibling() { if (isLeftOfParent()) return parent.right; if (isRightOfParent()) return parent.left; return null; } @Override public String toString() { String red = (color == RED ? "R" : ""); return red + "{" + key + "_" + value + "}"; } } public void print() { if (size == 0) return; for (int i = 0; i < table.length; i++) { final Node root = table[i]; System.out.println("【index = " + i + "】"); BinaryTrees.println(new BinaryTreeInfo() { @Override public Object string(Object node) { return node; } @Override public Object root() { return root; } @Override public Object right(Object node) { return ((Node ) node).right; } @Override public Object left(Object node) { return ((Node ) node).left; } }); System.out.println("---------------------------------------------------"); } } }
TreeMap:O(logn)
HashMap:O(1)
linkedHashMap 实现原理 删除注意点 更换节点的连接位置 注意点-
LiknedHashMap中维护的是双向链表(老师图中画的是单向的)
-
图中老师只画了一颗红黑树串起来的情况,实际上,节点之间是可以跨树(一个索引下就有一颗红黑树)串起来的,因为都会调用createNewNode方法
-
删除度为2的节点时,交换的是节点在链表中的位置
public class linkedHashMap参考extends HashMap { private linkedNode first; private linkedNode last; @Override public void clear() { super.clear(); first = null; last = null; } @Override public boolean containsValue(V value) { linkedNode node = first; while (null != node) { if (Objects.equals(value, node.value)) return true; node = node.next; } return false; } @Override public void traversal(Visitor visitor) { if (null == visitor) return; linkedNode node = first; while (null != node) { if (visitor.visit(node.key, node.value)) return; node = node.next; } } @Override protected Node createNewNode(K key, V value, Node parent) { linkedNode node = new linkedNode(key, value, parent); if (first == null) { first = node; last = node; } else { last.next = node; node.prev = last; last = node; } return node; } protected void removeAfter(Node willNode, Node removedNode) { linkedNode linkedWillNode = (linkedNode ) willNode; linkedNode linkedRemoveNode = (linkedNode ) removedNode; if (linkedWillNode != linkedRemoveNode) { // 交换prev linkedNode tmp = linkedWillNode.prev; linkedWillNode.prev = linkedRemoveNode.prev; linkedRemoveNode.prev = tmp; if (linkedWillNode.prev == null) { first = linkedWillNode; } else { linkedWillNode.prev.next = linkedWillNode; } if (linkedRemoveNode.prev == null) { first = linkedRemoveNode; } else { linkedRemoveNode.prev.next = linkedRemoveNode; } // 交换next tmp = linkedWillNode.next; linkedWillNode.next = linkedRemoveNode.next; linkedRemoveNode.next = tmp; if (linkedWillNode.next == null) { last = linkedWillNode; } else { linkedWillNode.next.prev = linkedWillNode; } if (linkedRemoveNode.next == null) { last = linkedRemoveNode; } else { linkedRemoveNode.next.prev = linkedRemoveNode; } } linkedNode prev = linkedRemoveNode.prev; linkedNode next = linkedRemoveNode.next; if (prev == null) { first = next; } else { prev.next = next; } if (next == null) { last = prev; } else { last.prev = prev; } } protected class linkedNode extends Node { linkedNode prev; linkedNode next; linkedNode(K key, V value, Node parent) { super(key, value, parent); } } }
小码哥李明杰老师课程: 恋上数据结构与算法 第一季.
本文完,感谢您的关注支持!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)