数据结构--哈希表(十)

以下是学习恋上数据结构与算法的记录要内容是哈希表
◼哈希表(Hash Table)

由于TreeMap特点:●Key 必须具备可比较性●元素的分布是有顺序的

在实际应用中,很多时候的需求,Map 中存储的元素不需要讲究顺序,Map 中的Key 不需要具备可比较性,不考虑顺序、不考虑Key 的可比较性,Map 有更好的实现方案,平均时间复杂度可以达到O(1),那就是采取哈希表来实现Map。

哈希表也叫做散列表(hash 有“剁碎”的意思)
在这里插入图片描述
添加、搜索、删除的流程都是类似的:
1.利用哈希函数生成key 对应的index【O(1)】
2.根据index 操作定位数组元素【O(1)】
●哈希表是【空间换时间】的典型应用●哈希函数,也叫做散列函数
●哈希表内部的数组元素,很多地方也叫Bucket(桶),整个数组叫Buckets 或者Bucket Array

◼哈希冲突(Hash Collision)
哈希冲突也叫做哈希碰撞:2 个不同的key,经过哈希函数计算出相同的结果,key1 ≠ key2 ,hash(key1) =hash(key2)
在这里插入图片描述
◼解决哈希冲突的常见方法
1.开放定址法(Open Addressing),按照一定规则向其他地址探测,直到遇到空桶
2.再哈希法(Re-Hashing),设计多个哈希函数
3.链地址法(Separate Chaining),比如通过链表将同一index的元素串起来

◼JDK1.8的哈希冲突解决方案
在这里插入图片描述
●默认使用单向链表将元素串起来
●在添加元素时,可能会由单向链表转为红黑树来存储元素,比如当哈希表容量≥ 64 且单向链表的节点数量大于8 时
●当红黑树节点数量少到一定程度时,又会转为单向链表
◼JDK1.8中的哈希表是使用链表+红黑树解决哈希冲突,这里为什么使用单链表?因为每次都是从头节点开始遍历,而单向链表比双向链表少一个指针,可以节省内存空间

◼哈希函数
●良好的哈希函数,会让哈希值更加均匀分布→减少哈希冲突次数→提升哈希表的性能
●哈希表中哈希函数的实现步骤大概如下
1.先生成key 的哈希值(必须是整数并且可以进一步处理,扰动计算)
2.再让key 的哈希值跟数组的大小进行相关运算,生成一个索引值
●为了提高效率,可以使用& 位运算取代% 运算【前提:将数组的长度设计为2 的幂(2n)】

private int hash(K key) {//哈希值
		if(key==null) return 0;
		int hash = key.hashCode(); //该hashCode方法为JDK计算哈希值的方法
		return hash ^ (hash >>> 16);//扰动计算:hash ^ (hash >>> 16)
	}
	private int index(K key) {//索引值
		return hash(key) & (table.length-1);
	}

◼如何生成key的哈希值
●key 的常见种类可能有:整数、浮点数、字符串、自定义对象,不同种类的key,哈希值的生成方式不一样,但目标是一致的
✓尽量让每个key 的哈希值是唯一的✓尽量让key 的所有信息参与运算
●在Java中,HashMap 的key 必须实现hashCode、equals 方法,也允许key 为null
◼整数
整数值当做哈希值:比如10 的哈希值就是10
在这里插入图片描述
◼浮点数
将存储的二进制格式转为整数值
在这里插入图片描述◼Long和Double的哈希值
在这里插入图片描述
●>>> 和^ 的作用是?
高32bit 和低32bit 混合计算出32bit 的哈希值,充分利用所有信息计算出哈希值

◼字符串的哈希值
●字符串是由若干个字符组成的:比如字符串jack,由j、a、c、k 四个字符组成(字符的本质就是一个整数),因此,jack 的哈希值可以表示为j∗n3+a∗n2+c∗n1+k∗n0,等价于[(j∗n+a)∗n+c]∗n+k
在这里插入图片描述
◼关于31的探讨
●31 * i= (2^5 –1) * i= i * 2^5 –i = (i << 5) –i
●31不仅仅是符合2^n –1,它是个奇素数(既是奇数,又是素数,也就是质数)
素数和其他数相乘的结果比其他方式更容易产成唯一性,减少哈希冲突,最终选择31是经过观测分布结果后的选择

◼自定义对象的哈希值
在这里插入图片描述
◼自定义对象作为key,最好同时重写hashCode 、equals 方法
在这里插入图片描述
●equals :用以判断2 个key 是否为同一个key
✓自反性:对于任何非null 的x,x.equals(x)必须返回true
✓对称性:对于任何非null 的x、y,如果y.equals(x) 返回true,x.equals(y) 必须返回true
✓传递性:对于任何非null 的x、y、z,如果x.equals(y)、y.equals(z) 返回true,那么x.equals(z) 必须返回true
✓一致性:对于任何非null 的x、y,只要equals 的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y) 就会一致地返回true,或者一致地返回false
✓对于任何非null 的x,x.equals(null) 必须返回false。

●hashCode :必须保证equals 为true的2 个key 的哈希值一样,
反过来hashCode 相等的key,不一定equals 为true。
●不重写hashCode 方法只重写equals 会有什么后果?
✓可能会导致2 个equals 为true 的key 同时存在哈希表中

◼装填因子(Load Factor):
●装填因子计算:节点总数量/ 哈希表桶数组长度,也叫做负载因子
●在JDK1.8的HashMap中,如果装填因子超过0.75,就扩容为原来的2倍

◼TreeMapvs HashMap
●何时选择TreeMap?元素具备可比较性且要求升序遍历(按照元素从小到大)
●何时选择HashMap?无序遍历

红黑树实现哈希表,不采用单向链表

package com.bj.map;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;
@SuppressWarnings({"unchecked", "rawtypes"})
public class HashMap<K, V> implements Map<K, V> {
	private static final boolean RED=false;
	private static final boolean BLACK=true;
	private int size;
	private Node<K,V> [] table;
	private static final int DEFAULT_CAPACITY=1<<4;//16
	//装填因子
	private static final float DEFAULT_LOAO_FACTOR=0.75f;
	public HashMap() {
		table=new Node[DEFAULT_CAPACITY];
	}
	public int size() {
		return size;
	}
	public boolean isEmpty() {
		return size == 0;
	}
	public void clear() {
		if(size==0) return;
		size=0;
		for(int i = 0;i<table.length;i++) {
			table[i] = null;//把存储索引的数组清空即可
		}
	}
	public V put(K key, V value) {
		resize();
		int index = index(key);//索引位置
		// 取出index位置的红黑树根节点
		Node<K, V> root = table[index];
		if(root ==null) {
			root = new Node<>(key, value, null);
			table[index] = root;
			size++;
			fixAfterPut(root);
			return null;
		}
		// 添加新的节点到红黑树上面
		Node<K, V> parent = root;
		Node<K, V> node = root;
		int cmp =0;
		K k1 = key;
		int h1 = k1==null?0:k1.hashCode();
		Node<K, V> result= null;
		boolean searched = false;// 是否已经搜索过这个key
		do {
			parent = node;//记录父节点
			K k2 = node.key;
			int h2 = k2.hashCode();
			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 { // searched == false; 还没有扫描,然后再根据内存地址大小决定左右
				if((node.left!=null &&(result = node(node.left,k1))!=null) ||
						(node.right != null && (result = node(node.right, k1)) != null)) {
					node=result;
					cmp=0;
				}else {//不存在这个key
					searched = true;
					cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
				}
			}	
			if(cmp>0) {
				node=node.right;
			}else if(cmp<0) {
				node=node.left;
			}else {//0 相等覆盖
				V oldValue= node.value;//记录旧值
				node.key=key;
				node.value=value;
				node.hash=h1;
				return oldValue;
			}
		}while(node!=null);
		// 看看插入到父节点的哪个位置
		Node<K, V> newNode = new Node<>(key, value, parent);//创建新节点
		if (cmp > 0) {
			parent.right = newNode;
		} else {
			parent.left = newNode;
		}
		size++;
		// 新添加节点之后的处理
		fixAfterPut(newNode);
		return null;
	}
	public V get(K key) {
		Node<K, V> node = node(key);
		return node!=null?node.value:null;
	}
	public V remove(K key) {
		return remove(node(key));
	}
	public boolean containsKey(K key) {
		return node(key) != null;
	}
	public boolean containsValue(V value) {
		if(size==0) return false;
		Queue<Node<K, V>> queue = new LinkedList<>();
		for(int i = 0;i<table.length;i++) {
			if(table[i] == null) continue;
			queue.offer(table[i]);
			while(!queue.isEmpty()) {
				Node<K, V> node = queue.poll();
				if(Objects.equals(value, node.value)) return true;
				if (node.left != null) {
					queue.offer(node.left);
				}
				if (node.right != null) {
					queue.offer(node.right);
				}
			}
		}
		return false;
	}
	public void traversal(Visitor<K, V> visitor) {
        if (size == 0 || visitor == null) return;
		Queue<Node<K, V>> queue = new LinkedList<>();
		for (int i = 0; i < table.length; i++) {
			if (table[i] == null) continue;
			queue.offer(table[i]);
			while (!queue.isEmpty()) {
				Node<K, V> node = queue.poll();
				if (visitor.visit(node.key, node.value)) return;
				if (node.left != null) {
					queue.offer(node.left);
				}
				if (node.right != null) {
					queue.offer(node.right);
				}
			}
		}
	}
	private void resize() {
		// 装填因子 <= 0.75,,节点总数量/ 哈希表桶数组长度,也叫做负载因子,如果装填因子超过0.75,就扩容为原来的2倍
		if(size/table.length<=DEFAULT_LOAO_FACTOR) return;
		Node<K, V>[] oldTable = table;
		table=new Node[oldTable.length<<1];//扩容为原来的2倍
		Queue<Node<K, V>> queue = new LinkedList<>();
		for(int i = 0;i<oldTable.length;i++) {
			if(oldTable[i]==null) continue;
			queue.offer(oldTable[i]);
			while(!queue.isEmpty()) {
				Node<K, V> node = queue.poll();
				if (node.left != null) {
					queue.offer(node.left);
				}
				if (node.right != null) {
					queue.offer(node.right);
				}
				// 挪动代码得放到最后面
				moveNode(node);
			}
		}
	}
	private void moveNode(Node<K, V> newNode) {
		// 重置
		newNode.parent = null;
		newNode.left = null;
		newNode.right = null;
		newNode.color = RED;
		int index = index(newNode);
		// 取出index位置的红黑树根节点
		Node<K, V> root = table[index];
		if (root == null) {
			root = newNode;
			table[index] = root;
			fixAfterPut(root);
			return;
		}
		// 添加新的节点到红黑树上面
		Node<K, V> parent = root;
		Node<K, V> node = root;
		int cmp = 0;
		K k1 = newNode.key;
		int h1 = newNode.hash;
		do {
			parent = node;
			K k2 = node.key;
			int h2 = node.hash;
			if (h1 > h2) {
				cmp = 1;
			} else if (h1 < h2) {
				cmp = -1;
			} else if (k1 != null && k2 != null 
					&& k1 instanceof Comparable
					&& k1.getClass() == k2.getClass()
					&& (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 (node != null);
		// 看看插入到父节点的哪个位置
		newNode.parent = parent;
		if (cmp > 0) {
			parent.right = newNode;
		} else {
			parent.left = newNode;
		}
		
		// 新添加节点之后的处理
		fixAfterPut(newNode);
	}
	private Node<K, V> node(K key){
		Node<K, V> root = table[index(key)];
		return root == null?null:node(root,key);
	}
	private Node<K, V> node(Node<K, V> node, K k1) {
		int h1 = hash(k1);
		//存储查找结果
		Node<K, V> result = null;
		int cmp = 0;
		while (node!=null) {
			K k2 = node.key;
			int h2 = node.hash;
			//先比较哈希值
			if(h1>h2) {
				node=node.right;
			}else if(h1<h2) {
				node=node.left;
			}else if(Objects.equals(k1, k2)) {//比较equals
				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.right!=null && (result = node(node.right,k1))!=null) {//扫描右子树
				return result;
			}else {
				// 只能往左边找
				node = node.left;
			}
//			} else if (node.left != null && (result = node(node.left, k1)) != null) { 
//			return result;
//		    } else {
//			return null;
//		    }
	}
	return null;
	}
	/**哈希函数实现步骤:一先生成Key的哈希值(必须是整数),二再让key的哈希值跟数组的大小进行相关运算,生成索引值
	 * 根据key生成对应的索引(在桶数组中的位置)
	 *  (hash ^ (hash >>> 16))为扰动计算
	 *  & 位运算,取代%运算(前提是数组长度为2的幕2^n),
	 */
	private int hash(K key) {
		if(key==null) return 0;
		int hash = key.hashCode(); 
		return hash ^ (hash >>> 16);
	}
	private int index(K key) {
		return hash(key) & (table.length-1);
	}
	private int index(Node<K, V> node) {
		return node.hash & (table.length-1);
	}
	private V remove(Node<K, V> node) {
		if (node == null) return null;
		size--;
		V oldValue = node.value;
		if (node.hasTwoChildren()) { // 度为2的节点
			// 找到后继节点
			Node<K, V> s = successor(node);
			// 用后继节点的值覆盖度为2的节点的值
			node.key = s.key;
			node.value = s.value;
			node.hash = s.hash;
			// 删除后继节点
			node = s;
		}
		// 删除node节点(node的度必然是1或者0)
		Node<K, V> replacement = node.left != null ? node.left : node.right;
		int index = index(node);
		if (replacement != null) { // node是度为1的节点
			// 更改parent
			replacement.parent = node.parent;
			// 更改parent的left、right的指向
			if (node.parent == null) { // node是度为1的节点并且是根节点
				table[index] = replacement;
			} else if (node == node.parent.left) {
				node.parent.left = replacement;
			} else { // node == node.parent.right
				node.parent.right = replacement;
			}
			// 删除节点之后的处理
			fixAfterRemove(replacement);
		} else if (node.parent == null) { // node是叶子节点并且是根节点
			table[index] = null;
		} else { // node是叶子节点,但不是根节点
			if (node == node.parent.left) {
				node.parent.left = null;
			} else { // node == node.parent.right
				node.parent.right = null;
			}
			// 删除节点之后的处理
			fixAfterRemove(node);
		}
		return oldValue;
	}
	private Node<K, V> successor(Node<K, V> node) {
		if (node == null) return null;
		// 前驱节点在左子树当中(right.left.left.left....)
		Node<K, V> p = node.right;
		if (p != null) {
			while (p.left != null) {
				p = p.left;
			}
			return p;
		}
		// 从父节点、祖父节点中寻找前驱节点
		while (node.parent != null && node == node.parent.right) {
			node = node.parent;
		}
		return node.parent;
	}
	private void  fixAfterRemove(Node<K, V> node) {
		// 如果删除的节点是红色
		// 或者 用以取代删除节点的子节点是红色
		if (isRed(node)) {
			black(node);
			return;
		}
		Node<K, V> parent = node.parent;
		if (parent == null) return;
		// 删除的是黑色叶子节点【下溢】
		// 判断被删除的node是左还是右
		boolean left = parent.left == null || node.isLeftChild();
		Node<K, V> sibling = left ? parent.right : parent.left;
		if (left) { // 被删除的节点在左边,兄弟节点在右边
			if (isRed(sibling)) { // 兄弟节点是红色
				black(sibling);
				red(parent);
				rotateLeft(parent);
				// 更换兄弟
				sibling = parent.right;
			}
			// 兄弟节点必然是黑色
			if (isBlack(sibling.left) && isBlack(sibling.right)) {
				// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
				boolean parentBlack = isBlack(parent);
				black(parent);
				red(sibling);
				if (parentBlack) {
					fixAfterRemove(parent);
				}
			} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
				// 兄弟节点的左边是黑色,兄弟要先旋转
				if (isBlack(sibling.right)) {
					rotateRight(sibling);
					sibling = parent.right;
				}
			   color(sibling, colorOf(parent));
				black(sibling.right);
				black(parent);
				rotateLeft(parent);
			}
		} else { // 被删除的节点在右边,兄弟节点在左边
			if (isRed(sibling)) { // 兄弟节点是红色
				black(sibling);
				red(parent);
				rotateRight(parent);
				// 更换兄弟
				sibling = parent.left;
			}
			// 兄弟节点必然是黑色
			if (isBlack(sibling.left) && isBlack(sibling.right)) {
				// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
				boolean parentBlack = isBlack(parent);
				black(parent);
				red(sibling);
				if (parentBlack) {
					fixAfterRemove(parent);
				}
			} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
				// 兄弟节点的左边是黑色,兄弟要先旋转
				if (isBlack(sibling.left)) {
					rotateLeft(sibling);
					sibling = parent.left;
				}
				color(sibling, colorOf(parent));
				black(sibling.left);
				black(parent);
				rotateRight(parent);
			}
		}
	}
	private void fixAfterPut (Node<K, V> node) {
		Node<K, V> parent = node.parent;
		// 添加的是根节点 或者 上溢到达了根节点
		if (parent == null) {
			black(node);
			return;
		}
		// 如果父节点是黑色,直接返回
		if (isBlack(parent)) return;
		// 叔父节点
		Node<K, V> uncle = parent.sibling();
		// 祖父节点
		Node<K, V> grand = red(parent.parent);
		if (isRed(uncle)) { // 叔父节点是红色【B树节点上溢】
			black(parent);
			black(uncle);
			// 把祖父节点当做是新添加的节点
			fixAfterPut(grand);
			return;
		}
		// 叔父节点不是红色
		if (parent.isLeftChild()) { // L
			if (node.isLeftChild()) { // LL
				black(parent);
			} else { // LR
				black(node);
				rotateLeft(parent);
			}
			rotateRight(grand);
		} else { // R
			if (node.isLeftChild()) { // RL
				black(node);
				rotateRight(parent);
			} else { // RR
				black(parent);
			}
			rotateLeft(grand);
		}
	}
	private void rotateLeft(Node<K, V> grand) {
		Node<K, V> parent = grand.right;
		Node<K, V> child = parent.left;
		grand.right = child;
		parent.left = grand;
		afterRotate(grand, parent, child);
	}
	private void rotateRight(Node<K, V> grand) {
		Node<K, V> parent = grand.left;
		Node<K, V> child = parent.right;
		grand.left = child;
		parent.right = grand;
		afterRotate(grand, parent, child);
	}
	private void afterRotate(Node<K, V> grand, Node<K, V> parent, Node<K, V> child) {
		// 让parent称为子树的根节点
		parent.parent = grand.parent;
		if (grand.isLeftChild()) {
			grand.parent.left = parent;
		} else if (grand.isRightChild()) {
			grand.parent.right = parent;
		} else { // grand是root节点
			table[index(grand)] = parent;
		}
		// 更新child的parent
		if (child != null) {
			child.parent = grand;
		}
		// 更新grand的parent
		grand.parent = parent;
	}
	private Node<K, V> color(Node<K, V> node, boolean color) {
		if (node == null) return node;
		node.color = color;
		return node;
	}
	private Node<K, V> red(Node<K, V> node) {
		return color(node, RED);
	}
	private Node<K, V> black(Node<K, V> node) {
		return color(node, BLACK);
	}
	private boolean colorOf(Node<K, V> node) {
		return node == null ? BLACK : node.color;
	}
	private boolean isBlack(Node<K, V> node) {
		return colorOf(node) == BLACK;
	}
	private boolean isRed(Node<K, V> node) {
		return colorOf(node) == RED;
	}
	protected static class Node<K,V>{
		int hash;
		K key;
		V value;
		boolean color = RED;
		Node<K,V> left;
		Node<K,V> right;
		Node<K,V> parent;
		public Node(K key, V value,Node<K, V> parent) {
			this.key = key;
			int hash = key == null ? 0 : key.hashCode();
			this.hash = hash ^ (hash >>> 16);//提前生死哈希值,减少其他地方的计算次数
			this.value = value;
			this.parent = parent;
		}
		public boolean hasTwoChildren() {
			return left != null && right != null;
		}
		public boolean isLeftChild() {
			return parent != null && this == parent.left;
		}
		public boolean isRightChild() {
			return parent != null && this == parent.right;
		}
		public Node<K, V> sibling() {
			if (isLeftChild()) {
				return parent.right;
			}
			if (isRightChild()) {
				return parent.left;
			}
			return null;
		}
		@Override
		public String toString() {
			return "Node_" + key + "_" + value;
		}
		
	}

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值