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就相同