赞
踩
一、哈希表
哈希表(hash table):哈希表是基于数组取下标的方式来快速进行增删改查的一种数据结构。
哈希函数:将 key 映射成数组下标的函数。
哈希冲突:不同的 key ,映射到相同的下标上。
处理哈希冲突的方法:①闭散列;②开散列。
闭散列:
1)若出现哈希冲突,则继续往后找下一个空闲位置。(可将int类型的数组换为对象数组);
2) 若数组上元素较多,比较拥挤,此时算法性能严重下降,就需频繁对数组进行扩容,使得数组上保持稀疏。
开散列(哈希桶):
1) 数组上的每个元素为一个链表的头节点,若出现哈希冲突,就将此对象插入到对应位置的链表上;
2) 使用开散列也可能会导致每个下标位置的链表过长,此时可通过:①扩容;②将这个较长的链表转换成红黑树或哈希表;
3)字符串映射的哈希值求解算法:md5、md4、sha1、sha256…;
4)md5 计算哈希值的特点:
①定长。得到的哈希值都是固定长度;
②分散。输入字符串有微小变化,得到的哈希值差别都会很大;(若字符串 str1 和 str2 的 md5 值相同,则可认为 str1 和 str2 相同。)
③不可逆。可根据字符串计算 md5 值,但根据 md5 值找到对应的字符串原串,理论上是不可能的。
md5 的应用:
①作为字符串hash值的算法;
②用于加密领域;
③校验文件传输结果是否正确;(对比文件传输前后的md5 值)
5)负载因子:衡量当前哈希表中的元素拥挤程度,可用来判断当前数组是否需要进行扩容。
负载因子的选取:通常是根据实验来选取的。负载因子太小,空间利用率越低;负载因子太大,性能效率会降低。
6)性能分析:虽然哈希表一直在和冲突做斗争,但在实际使用过程中,哈希表的冲突率是不高的,冲突个数是可控的,也就是每个桶中的链表的长度是一个常数,所以,通常意义下,哈希表的插入/删 除/查找时间复杂度是 O(1) 。
哈希表的实现:
以下采用开散列/哈希桶的方式来处理哈希冲突。
class HashNode{ public int key; public int value; public HashNode next; public HashNode(int key,int value) { this.key = key; this.value = value; } } //哈希表:数组上的每个元素是一个链表 //采用开散列/哈希桶的方式来处理哈希冲突 public class MyHashMap { private HashNode[] array = new HashNode[16]; private int size = 0; //1.插入键值对 public void put(int key,int value){ //根据key,计算下标位置 int index = key % array.length; //查看index位置的链表中是否存在key,存在直接修改value,不存在插入新节点。 for(HashNode cur = array[index];cur != null;cur = cur.next){ if(cur.key == key){ cur.value = value; return; } } //循环结束,即没找到,需创建新节点插入到链表中(此处采用头插) HashNode newNode = new HashNode(key,value); newNode.next = array[index]; array[index] = newNode; size++; if(loadFactor() > 0.75){ resize(); } } private double loadFactor(){ return size / array.length; } private void resize(){ //创建一个更长的新数组,将原数组拷贝进去 HashNode[] newArray = new HashNode[2 * array.length]; for(int i = 0;i < array.length;i++){ HashNode next = null; //外层循环拷贝数组 //for(HashNode cur = array[i];cur != null;cur = cur.next){ for(HashNode cur = array[i];cur != null;cur = next){ next = cur.next;//修改cur之前需提前备份之前的位置, // 下面的cur.next已指向的是新链表,不能当做循环条件中的旧链表指向了 //里层循环拷贝i下标位置的链表 int indexNew = cur.key % newArray.length; cur.next = newArray[indexNew]; newArray[i] = cur; } } array = newArray; } //2.根据key获取value public Integer get(int key){ int index = key % array.length; //遍历哈希表寻找key for(int i = 0;i < array.length;i++){ for(HashNode cur = array[index];cur != null;cur = cur.next){ if(cur.key == key){ return cur.value; } } } return null; } //3.删除key public void remove(int key){ int index = key % array.length; for(int i = 0;i < array.length;i++){ HashNode prev = array[index]; while(prev != null && prev.next != null && prev.next.key == key){ prev = prev.next; } if(prev == null && prev.next == null){ return; } HashNode cur = prev.next; prev.next = cur.next; } } }
二、搜索树:
删除元素的8种情况:
二叉搜索树的简单实现:
class BinaryNode{ public int key; public int value; public BinaryNode left; public BinaryNode right; public BinaryNode(int key,int value) { this.key = key; this.value = value; } } public class BinarySearchTree { private BinaryNode root = null; //1.查找节点 public Integer get(int key){ //创建一个引用cur 从root出发 BinaryNode cur = root; while(cur != null){ if(key < cur.key ){ cur = cur.left; }else if(key > cur.key){ cur = cur.right; }else{ return cur.value; } } return null; } //2.插入节点 public void put(int key,int value){ if(root == null){ root = new BinaryNode(key,value); return; } //先找到要插入节点的位置 BinaryNode cur = root; BinaryNode parent = null; while(cur != null){ if(key < cur.key){ parent = cur; cur = cur.left; }else if(key > cur.key){ parent = cur; cur = cur.right; }else{ cur.value = value; return; } } BinaryNode newNode = new BinaryNode(key,value); if(key < parent.key){ parent.left = newNode; }else{ parent.right = newNode; } } //3.删除节点 public void remove(int key){ //先查找待删除节点的位置 BinaryNode cur = root; BinaryNode parent = null; while(cur != null){ if(key < cur.key){ parent = cur; cur = cur.left; }else if(key > cur.key){ parent = cur; cur = cur.right; }else{ removeNode(parent,cur); return; } } } private void removeNode(BinaryNode parent,BinaryNode cur){ if(cur.left == null){ //1.待删除节点的左子树为空 if(cur == root){ //1.1待删除节点是根节点 root = cur.right; }else if(cur == parent.left){ //1.2待删除节点不是根节点,是父节点的左节点 parent.left = cur.right; }else if(cur == parent.right){ //1.3待删除节点不是根节点,是父节点的右节点 parent.right = cur.right; } }else if(cur.right == null){ //2.待删除节点的右子树为空 if(cur == root){ //2.1待删除节点是根节点 root = cur.left; }else if(cur == parent.left){ //2.2待删除节点是父节点的左节点 parent.left = cur.left; }else if(cur == parent.right){ //2.3待删除节点是父节点的右节点 parent.right = cur.left; } }else{ //3.待删除节点的左右子树都不为空 //需在其右子树中找一个最小节点作为替罪羊节点 //将替罪羊节点复制给待删除节点,再将原替罪养节点删除 BinaryNode goat = cur.right; BinaryNode goatParent = null; while(goat.left != null){ goatParent = goat; goat = goat.left; } cur.key = goat.key; cur.value = goat.value; if(goat == goatParent.left){ goatParent.left = goat.right; }else{ goatParent.right = goat.right; } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。