【Java】Map与Set

本文详细介绍了二叉搜索树(包括AVL树和红黑树)的搜索、插入和删除操作,以及TreeMap和TreeSet的区别。同时讨论了哈希表(如HashMap)的工作原理,以及如何使用HashSet实现去重。最后提到了hashCode和equals方法的重要性。
摘要由CSDN通过智能技术生成
1、二叉搜索树

二叉搜索树-》AVL树-》红黑树
(1)搜索的时间复杂度
最好情况:完全二叉树 O(logN)
最坏情况:单分支二叉树 O(N)

public boolean search(int val){
	TreeNode cur = root;
	while(cur != null){
		if(cur.val < key){
			cur = cur.right;
		}else if(cur.val > key){
			cur = cur.left;
		}else{
			return true;
		}
	}
	return false;
}

(2)插入

public static boolean insert(int val){
	if(root == val){
		root = new TreeNode(val);
		return true;
	}
	TreeNode cur = root;
	TreeNode parent = null;
	while(cur != null){
		if(cur.val < val){
			parent = cur;
			cur = cur.right;
		}else if(cur.val > val){
			parent = cur;
			cur = cur.left;
		}else{
			return false;
			//二叉搜索树里面不需要有相等的数据
		}
	}
	TreeNode node = new TreeNode(val);
	if(parent.val > val){
		parent.left = node;
	}else{
		parent.right = node;
	}
	return true;
}

(3)删除

public void remove(int val){
	TreeNode cur = root;
	TreeNode parent = null;
	while(cur != null){
		if(cur.val < key){
			parent = cur;
			cur = cur.right;
		}else if(cur.val > key){
			parent = cur;
			cur = cur.left;
		}else{
			removeNode(cur,parent);
		}
	}
}

public void removeNode(TreeNode cur,TreeNode 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{
		TreeNode targetParent = cur;
		TreeNode target = cur.right;
		while(target.left != null){
			targetParent = target;
			target = target.left;
		}
		//替换
		cur.val = target.val;
		//删除target
		if(targetParent.left == target){
			targetParent.left = target.right;
		}else{
			targetParent.right = target.right;
			//这里指target第一部就找到了
		}
	}
	//两边都不为空这种情况
	//删除这里替换的数据比左树都大,比右树都小
	//要么在左树找到最大的数据(即左树最右边的数据),要么在右树里面找到最小的数据(即右树最左边的数据)
	//找到合适的数据后,替换cur的值,并删除那个数据结点
}

(4)AVL树:高度平衡的二叉搜索树
一旦发现二叉搜索树左右高度差大于一,就会旋转进行平衡
旋转分为左旋、右旋、左旋右旋、右旋左旋

2、TreeMap与TreeSet

(1)TreeMap
· Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap
· Map中存放键值对的Key是唯一的,value是可以重复的
· 在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常,value可以为空。但是HashMap的key和value都可以为空。
· Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
· Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
· Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。

TreeMap<String,Integer> map = new Treemap<>();
map.put("Sunny",3);
Integer val = map.get("Sunny");
//get方法通过key获取val
Integer val1 = map.getOrDefault("Sunny",999);
//如果没有这个key,则返回默认值999
Set<String> set = map.keySet();
//获得所有的key得到一个Set
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();

(2)Set

Set<String> set = new TreeSet<>();
set.add("sunny");
3、哈希表

(1)不经过任何比较,一次直接从表中得到要搜索的元素。使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
(2)哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(或者称散列表)。
(3)哈希冲突
· 不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞
· 哈希冲突是必然发生的
(4)哈希冲突的避免
· 哈希函数设计:包括全部需要存储的关键码
· 分布尽量均匀
· 哈希函数尽量简单
(5)哈希函数设计
· 直接定制法
· 除留余数法
· 冲突-避免-负载因子调节法
(4)散列表的载荷因子 = 填入表中的元素个数 / 散列表长度
· 载荷因子越高,冲突率越高
· 为了降低载荷因子,我们一般增加散列表长度(扩容)
(5)冲突-解决-闭散列(也叫开放地址法):效率不高
· 线性探测:将可能冲突的元素都放在一起了
· 二次探测:Hi = (H0 + i^2) % m
其中i是次数
(4)冲突-解决-开散列/哈希桶(开散列法又叫链地址法(开链法))
· 将冲突的元素连在链表上:JDK1.7之前采用头插法,1.8开始采用尾插法
· 数组+链表+红黑树 (当数组长度>=64 && 链表长度 >= 8,把链表变成红黑树)
当载荷因子过大时,要给数组进行扩容;注意的是:扩容后,要重新哈希,因为原来冲突的位置可能放在数组其他位置了

4、map与Set代码

(1)map的基本用法
创建

HashMap<String,Integer> map = new HashMap<>();

put

map.put("哈利",1);  
map.put("罗恩",2);  
map.put("赫敏",3);  
map.put("卢娜",4);

get

System.out.println(map.get("罗恩"));  
System.out.println(map.get("金妮"));
//打印结果为
//2  null

(2)map的遍历
方法一:直接打印

System.out.println(map);
//打印结果为:{卢娜=4, 赫敏=3, 哈利=1, 罗恩=2}

方法二:用entrySet()打印

System.out.println(map.entrySet());
//打印结果为:[卢娜=4, 赫敏=3, 哈利=1, 罗恩=2]

方法三:用for each打印
for each格式

for(元素类型t 元素变量x : 遍历对象obj){ 
     引用了x的java语句; 
} 

for each 例子

//这里array是一个int[]类型的数组名
for(int a : array){
	System.out.print(a+ " ");
}

最终写法

for (Map.Entry<String,Integer> entry : map.entrySet()){ 
    System.out.print(entry + " ");  
}
//打印结果为:卢娜=4 赫敏=3 哈利=1 罗恩=2 

或者

for (Map.Entry<String,Integer> entry : map.entrySet()){ 
    System.out.print(entry.getKey() + "+" + entry.getValue() + " ");  
}

(3)HashMap的类可以放自己指定的类,因为放进去的时候不用比较大小;但是TreeMap不行,会报错,因为TreeMap放进去时要比较大小

5、HashSet用法

可以天然去重
(1)基本用法
创建

HashSet<String> set = new HashSet<>();

add

set.add("邓布利多");  
set.add("麦格");  
set.add("斯内普");  
set.add("小天狼星");

删除

set.remove("斯内普");

判断元素是否存在

System.out.println(set.contains("斯内普"));

计算大小

System.out.println(set.size());

(2)遍历
直接遍历

System.out.println(set);

for each遍历

for (String str : set) {  
    System.out.print(str + " ");  
}
6、HashCode方法

每次拿到一个类最好定义它的equals方法和HashCode方法

public class Main {  
    public static void main(String[] args) {  
        Student student1 = new Student("12345");  
        Student student2 = new Student("12345");  
        HashMap<Student,Integer> map1 = new HashMap<>();  
        map1.put(student1,2);  
        System.out.println(map1.get(student2));  
    }  
  
}  
  
class Student{  
    public String id;  
  
    public Student(String id){  
        this.id = id;  
    }  
  
    @Override  
    public boolean equals(Object o) {  
        if (this == o) return true;  
        if (o == null || getClass() != o.getClass()) return false;  
        Student student = (Student) o;  
        return Objects.equals(id, student.id);  
    }  
  
    @Override  
    public int hashCode() {  
        return Objects.hash(id);  
    }  
}

重写hashcode之后,Student类只要id相同,获取到的hashcode就相同

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沙河板混

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值