目录
一、概念与模型
二、Map的使用
三、Set的说明
1.一些常用的方法
四、一些小练习
五、搜索树
1.概念
2.查找操作
3.插入操作
4.删除操作
六、哈希表
1.概念
2.冲突
方法
|
解释
|
K getKey()
|
返回 entry 中的 key
|
V getValue()
|
返回 entry 中的 value
|
V setValue(V value)
|
将键值对中的 value 替换为指定 value
|
3.Map 的常用方法说明
方法
|
解释
|
V get(Object key)
|
返回 key 对应的 value
|
V getOrDefault(Object key, V defaultValue)
|
返回 key 对应的 value , key 不存在,返回默认值
|
V put(K key, V value)
|
设置 key 对应的 value
|
V remove(Object key)
|
删除 key 对应的映射关系
|
Set |
返回所有 key 的不重复集合
|
Collection |
返回所有 value 的可重复集合
|
Set |
返回所有的 key-value 映射关系
|
boolean containsKey(Object key)
|
判断是否包含 key
|
boolean containsValue(Object value)
|
判断是否包含 value
|
Map 底层结构
|
TreeMap
|
HashMap
|
底层结构
|
红黑树 |
哈希桶
|
插入 / 删除 / 查找时间复杂度
|
O(logN) |
O(1)
|
是否有序
|
关于 Key 有序
|
无序
|
线程安全
|
不安全
|
不安全
|
插入 / 删除 / 查找区别
|
需要进行元素比较
|
通过哈希函数计算哈希地址
|
比较与覆写
|
key 必须能够比较,否则会抛出ClassCastException异常
|
自定义类型需要覆写 equals 和hashCode方法
|
应用场景
|
需要 Key 有序场景下
|
Key 是否有序不关心,需要更高的时间性能
|
方法
|
解释
|
boolean add(E e)
|
添加元素,但重复元素不会被添加成功
|
void clear()
|
清空集合
|
boolean contains(Object o)
|
判断 o 是否在集合中
|
Iterator |
返回迭代器
|
boolean remove(Object o)
|
删除集合中的 o
|
int size()
|
返回 set 中元素的个数
|
boolean isEmpty()
|
检测 set 是否为空,空返回 true ,否则返回 false
|
Object[] toArray()
|
将 set 中的元素转换为数组返回
|
boolean containsAll(Collection> c)
|
集合 c 中的元素是否在 set 中全部存在,是返回 true ,否则返回false
|
boolean addAll(Collection extends E> c)
|
将集合 c 中的元素添加到 set 中,可以达到去重的效果
|
Set 底层结构
|
TreeSet
|
HashSet
|
底层结构
|
红黑树
|
哈希桶
|
插入 / 删除 / 查找时间复杂度
|
O(logN) |
O(1)
|
是否有序
|
关于 Key 有序
|
不一定有序
|
线程安全
|
不安全
|
不安全
|
插入 / 删除 / 查找区别
|
按照红黑树的特性来进行插入和删除
|
①先计算 key 哈希地址② 然后进行插入和删除
|
比较与覆写
|
key 必须能够比较,否则会抛出ClassCastException异常
|
自定义类型需要覆写 equals 和hashCode方法
|
应用场景
|
需要 Key 有序场景下
|
Key 是否有序不关心,需要更高的时间性能
|
1.给定10W个数据,统计每个数据出现的次数
public class TestDemo {
public static Map func(int[] arr){
Map map = new HashMap<>();
for (int x:arr) {
if(map.get(x) == null){
map.put(x,1);
}else{
int val = map.get(x);
map.put(x,val+1);
}
}
return map;
}
public static void main(String[] args) {
int[] arr = new int[10_0000];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(1000);
}
Map map = func(arr);
System.out.println(map);
}
}
2.将10W个数据中的数据去重
把数据放到Set即可
public static Set func2(int[] arr){
HashSet set = new HashSet<>();
for (int x:arr) {
set.add(x);
}
return set;
}
3.从10W个数据中找到第一个重复的数据
public static int func3(int[] arr){
HashSet set = new HashSet<>();
for (int x:arr) {
if(set.contains(x)){
return x;
}
set.add(x);
}
return -1;
}
class Node{
public int val;
public Node left;
public Node right;
public Node(int val){
this.val = val;
}
}
public class BinarySearchTree {
Node root = null;
public Node search(int key){
Node cur = root;
while(cur != null){
if(cur.val < key){
cur = cur.right;
}else if(cur.val > key){
cur = cur.left;
}else{
return cur;
}
}
return null;//没有这个数据
}
}
class Node{
public int val;
public Node left;
public Node right;
public Node(int val){
this.val = val;
}
}
public class BinarySearchTree {
Node root = null;
public boolean insert(int val){
if(root == null){
root = new Node(val);
return true;
}
Node cur = root;
Node parent = null;
while(cur != null){
if(cur.val < val){
parent = cur;
cur = cur.right;
}else if(cur.val == val){
return false;//不能有相同数据进行插入
}else{
parent = cur;
cur = cur.left;
}
}
Node node = new Node(val);
if(parent.val < val){
parent.right = node;
}else{
parent.left = node;
}
return true;
}
}
class Node{
public int val;
public Node left;
public Node right;
public Node(int val){
this.val = val;
}
}
public class BinarySearchTree {
Node root = null;
public void remove(int key){
Node cur = root;
Node parent = null;
while(cur != null){
if(cur.val == key){
removeNode(cur,parent);
break;
}else if(cur.val < key){
parent = cur;
cur = cur.right;
}else{
parent = cur;
cur = cur.left;
}
}
}
public void removeNode(Node cur,Node parent){
if(cur.left == null){
if(cur == root){
root = cur.right;
}else if(cur == parent.left){
parent.left = cur.right;
}else{
parent.right = cur.right;
}
}else if(cur.right == null){
if(cur == root){
root = cur.left;
}else if(cur == parent.left){
parent.left = cur.left;
}else{
parent.right = cur.left;
}
}else{
Node targetParent = cur;
Node target = cur.right;
while(target.left != null){
targetParent = target;
target = target.left;
}
cur.val = target.val;
if(target == targetParent.left){
targetParent.left = target.right;
}else {
targetParent.right = target.right;
}
}
}
public void inOrder(Node root){
if(root == null){
return;
}
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
}
①哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
②哈希函数计算出来的地址能均匀分布在整个空间中
③哈希函数应该比较简单
常见的哈希函数:
①直接定制法(常用):取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况
我们来看一条题:
代码实现:
class Solution {
public int firstUniqChar(String s) {
if(s == null){
return -1;
}
int[] arr = new int[26];
for(int i = 0; i < s.length(); i++){
char ch = s.charAt(i);
arr[ch-97]++;
}
for(int i = 0; i < s.length(); i++){
char ch = s.charAt(i);
if(arr[ch-97] == 1){
return i;
}
}
return -1;
}
}
研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。 因此:比散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷
(ii)开散列
代码实现:
public class HashBuck {
static class Node{
public int key;
public int val;
public Node next;
public Node(int key, int val){
this.key = key;
this.val = val;
}
}
public Node[] arr;
public int usedSize;
public static final double DEFAULT_LOADFactor = 0.75;
public HashBuck(){
this.arr = new Node[10];
}
public void put(int key, int val){
//找到key所在的位置
int index = key % this.arr.length;
//遍历下标的链表,看看是不是有相同的key,如果有,要更新val
Node cur = arr[index];
while(cur != null){
if(cur.key == key){
cur.val = val;
return;
}
cur = cur.next;
}
//没有key这个元素,头插法插入
Node node = new Node(key,val);
node.next = arr[index];
arr[index] = node;
this.usedSize++;
//插入元素成功后,检查当前散列表的负载因子
if(loadFactor() > DEFAULT_LOADFactor){
resize();//扩容----->不是简单的把数组扩大,数组里面的每个链表的每个元素必须重新进行哈希
//14%10=4 14%20=14
}
}
private void resize(){
Node[] newArr = new Node[arr.length*2];
for(int i = 0; i < this.arr.length; i++){
Node cur = arr[i];
while(cur != null){
int index = cur.key % newArr.length;//获取新的下标
//把cur这个节点,以头插或者尾插的方式插入到新的数组对应下标的链表当中
Node curNext = cur.next;
cur.next = newArr[index];//先绑定前面
newArr[index] = cur;//再绑定后面
cur = curNext;
}
}
arr = newArr;
}
private double loadFactor(){
return 1.0 * usedSize / this.arr.length;
}
public int get(int key){
//找到key所在的位置
int index = key % this.arr.length;
//遍历下标的链表,看看是不是有相同的key,如果有,要更新val
Node cur = arr[index];
while(cur != null){
if(cur.key == key){
return cur.key;
}
cur = cur.next;
}
return -1;
}
}
import java.util.HashMap;
import java.util.Objects;
class Person{
public String ID;
public Person(String ID){
this.ID = ID;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(ID, person.ID);
}
@Override
public int hashCode() {
return Objects.hash(ID);
}
@Override
public String toString() {
return "Person{" +
"ID='" + ID + '\'' +
'}';
}
}
public class HashBuck2 {
static class Node{
public K key;
public V val;
public Node next;
public Node(K key, V val){
this.key = key;
this.val = val;
}
}
public Node[] arr = (Node[])new Node[10];
public int usedSize;
public void put(K key, V val){
int hash = key.hashCode();//hashcode()寻找位置
int index = hash % arr.length;
Node cur = arr[index];
while(cur != null){
if(cur.key.equals(key)){//equals()负责查看有没有一样的元素
cur.val = val;
return;
}
cur = cur.next;
}
Node node = new Node(key,val);
node.next = arr[index];
arr[index] = node;
this.usedSize++;
}
//hashcode()一样,equals()不一定一样;equals()一样,hashcode()一定一样
public V get(K key){
int hash = key.hashCode();//hashcode()寻找位置
int index = hash % arr.length;
Node cur = arr[index];
while(cur != null){
if(cur.key.equals(key)){//equals()负责查看有没有一样的元素
return cur.val;
}
cur = cur.next;
}
return null;
}
public static void main(String[] args) {
Person person1 = new Person("123");
Person person2 = new Person("123");
HashBuck2 hashBuck2 = new HashBuck2();
hashBuck2.put(person1,"abc");
System.out.println(hashBuck2.get(person2));
}
}
编译运行该代码,输出如下:
abc
(4)一个小题
求下面的查找成功的平均长度以及查找不成功的平均长度: