TreeMap和TreeSet底层是用一颗搜索树实现的,不过这颗树是一颗红黑树,树的节点有黑色也有红色,我们现在只需要先了解一下什么是搜索树。
二叉搜索树:又称二叉排序树
查找:
若根节点key == 查找key 返回true;
若根节点key > 查找key 在其左子树查找;
若根节点key < 查找key 在其右子树查找;
否则返回 false。
public Node search(int key) {
Node cur = root;
while (cur != null) {
//1.若根节点key == 查找key,返回
if (key == cur.key) {
return cur;
//2.若根的key大于查找key去左子树
} else if (cur.key > key) {
cur = cur.left;
//3.若根的key大小于查找key去右子树
} else {
cur = cur.right;
}
}
//4.找不到返回null
return null;
}
插入:
分情况: 每次插入都是从根节点往下遍历
public boolean insert(int key) {
//1.若根节点是null,直接插入
if (root == null) {
root = new Node(key);
return true;
}
Node cur = root;
Node parent = null;
//2.遍历找到待插入key的合适位置
//待插入key,插入的位置肯定是一个根节点的叶子节点
//当cur为null,parent就是待插入key的根节点
while (cur != null) {
if (key == cur.key) {
return false;
} else if (key < cur.key) {
parent = cur;
cur = cur.left;
} else {
parent = cur;
cur = cur.right;
}
}
//3.判断待插入key比当前节点key的大小关系
//待插入key比根key大,就插入根节点右子树
//否则插入根节点左子树
Node node = new Node(key);
if (key < parent.key) {
parent.left = node;
} else {
parent.right = node;
}
return true;
}
删除:
分情况: 假设待删除节点为cur,待删除节点的父亲节点为parent。
cur.left == null;
cur 不是根节点(root),则根节点 == cur.right;
cur 不是根节点(root),cur是parent的left,则parent.left = cur.right;
cur 不是根节点(root),cur是parent.right,则parent.right = cur.right。
cur.right == null
cur不是根节点(root),cur是parent.left,则parent.left = cur.left;
cur不是根节点(root),cur 是 parent.right,则 parent.right = cur.left
cur.left != null && cur.right != null
需要使用一种替罪羊法来进行删除,即在当前要删除的节点的左子树的最右边找叶子节点(找到9),或者当前删除节点的右子树的最左边找叶子节点(找到16).
public boolean remove(int key) {
Node cur = root;
Node parent = null;
//1.遍历找到要删除key的位置
while (cur != null) {
if (cur.key == key) {
removeHelp(parent, cur);
return true;
} else if (cur.key > key) {
parent = cur;
cur = cur.left;
} else if (cur.key < key) {
parent = cur;
cur = cur.right;
}
}
return false;
}
private void removeHelp(Node parent,Node cur){
if(cur.left == null){
if(cur == root){
root = cur.right;
} else if (cur == parent.left) {
parent.left = cur.right;
}else if(cur == parent.right){
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 if(cur == parent.right){
parent.right = cur.left;
}
}else {
//1.从待删除节点的右子树的最左边找替罪羊
Node target = cur.right;//替罪羊节点
Node targetParent = cur;
while(target != null){
targetParent = target;
target = target.left;
}
//2.交换值
cur.key = target.key;
//3.判断,
//待删除节点的右树有没有左子树
if(target == targetParent.left){
targetParent.left = target.right;
}else {
targetParent.right = target.right;
}
}
结论:
插入和删除操作都必须先查找,即查找就代表了二叉搜索树的各个操作的性能。
对有N个节点的二叉搜索树,若每个元素查找的概率相等(满二叉树),则平均查找长度是节点在二叉搜索树的深度的函数(logn),即节点越深,比较次数越多。
对于同一组数据,如果插入的次序不同,可能得到不同结构的二叉搜索树。
public class BinarySearchTree {
public static class Node {
int key;
Node left;
Node right;
public Node(int key) {
this.key = key;
}
}
private Node root = null;
public Node search(int key) {
Node cur = root;
while (cur != null) {
//1.若根节点,返回
if (key == cur.key) {
return cur;
//2.若根的key大于查找key去左子树
} else if (cur.key > key) {
cur = cur.left;
//3.若根的key大小于查找key去右子树
} else {
cur = cur.right;
}
}
//4.找不到返回null
return null;
}
public boolean insert(int key) {
//1.若根节点是null,直接插入
if (root == null) {
root = new Node(key);
return true;
}
Node cur = root;
Node parent = null;
//2.遍历找到待插入key的合适位置
//待插入key,插入的位置肯定是一个根节点的叶子节点
//当cur为null,parent就是待插入key的根节点
while (cur != null) {
if (key == cur.key) {
return false;
} else if (key < cur.key) {
parent = cur;
cur = cur.left;
} else {
parent = cur;
cur = cur.right;
}
}
//3.判断待插入key比当前节点key的大小关系
//待插入key比根key大,就插入根节点右子树
//否则插入根节点左子树
Node node = new Node(key);
if (key < parent.key) {
parent.left = node;
} else {
parent.right = node;
}
return true;
}
public boolean remove(int key) {
Node cur = root;
Node parent = null;
//1.遍历找到要删除key的位置
while (cur != null) {
if (cur.key == key) {
removeHelp(parent, cur);
return true;
} else if (cur.key > key) {
parent = cur;
cur = cur.left;
} else if (cur.key < key) {
parent = cur;
cur = cur.right;
}
}
return false;
}
private void removeHelp(Node parent,Node cur){
if(cur.left == null){
if(cur == root){
root = cur.right;
} else if (cur == parent.left) {
parent.left = cur.right;
}else if(cur == parent.right){
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 if(cur == parent.right){
parent.right = cur.left;
}
}else {
//1.从待删除节点的右子树的最左边找替罪羊
Node target = cur.right;//替罪羊节点
Node targetParent = cur;
while(target != null){
targetParent = target;
target = target.left;
}
//2.交换值
cur.key = target.key;
//3.判断,
//待删除节点的右树有没有左子树
if(target == targetParent.left){
targetParent.left = target.right;
}else {
targetParent.right = target.right;
}
}
}
}
1.Map和set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。
2.Map:是一个接口类,该类没有继承自Collection,该类中存储的是
K和V的类型可以相同,也可以不同,K的类型一定要是可以比较的。
键值对:表示K和V是一一对应的关系,比如K=面包,V=10(元)。
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 keySet() | 返回所有 key 的不重复集合 |
Collection values() | 返回所有 value 的可重复集合 |
Set |
返回所有的 key-value 映射关系 |
boolean containsKey(Object key) | 判断是否包含 key,有返回true,否则false |
boolean containsValue(Object value) | 判断是否包含 value,有返回true,否则false |
Map是一个接口,不能直接实例化对象,如果要实例化对象只能通过实例化实现类TreeMap或HashMap。
HashMap
使用put方法时,如传的key存在,则只会把指定key所对应的value值,替换成指定的新值,而不会再添加一个key,返回值为key对应的value值。
使用get方法时,获取指定key所对应的value值。
remove方法,根据指定的key删除元素,返回被删除元素的value值。
TreeMap和HashMap的区别:
Map底层结构 | TreeMap | HashMap |
---|---|---|
底层结构 | 红黑树 | 哈希桶 |
插入/删除/查找时间 复杂度 | O(logN) | O(1) |
是否有序 | 关于Key有序 | 无序 |
线程安全 | 不安全 | 不安全 |
插入/删除/查找区别 | 需要进行元素比较 | 通过哈希函数计算哈希地址 |
比较与覆写 | key必须能够比较,否则会抛出 ClassCastException异常 | 自定义类型需要覆写equals和 hashCode方法 |
应用场景 | 需要Key有序场景下 | Key是否有序不关心,需要更高的 时间性能 |
HashMap方法演示:
public class Main {
public static void main(String[] args) {
//创建HashMap对象,key为String类型,value为Integer
HashMap<String,Integer> map = new HashMap<>();
//给map添加元素,每一次放入都会进行比较,第一次放入也会比较
map.put("奶茶",18);
map.put("面包",6);
map.put("咖啡",22);
map.put(null,null);
//因为value是Integer类型,可以使用int也可以Integer接收
//这涉及到拆包问题
int get1 = map.get("奶茶");//返回18
Integer get2 = map.get("咖啡");//返回22
//因为HashMap重写了toString
//输出:{null=null, 面包=6, 咖啡=22, 奶茶=18}
System.out.println(map);
Integer del = map.remove("奶茶");//返回对应value值
}
}
``
Set
Map.Entry
方法 | 解释 |
---|---|
K getKey() | 返回 entry 中的 key |
V getValue() | 返回 entry 中的 value |
V setValue(V value) | 将键值对中的value替换为指定value |
获取Map集合中,所有的Key和Value并组织起来,把相对的K和V作为一个整体,放入Set,组织起来的这个结构就叫做Map.Entry
使用方法:
可以直接通过for each 遍历原先的Map里的内容。如果不实现Set
Set的官方文档
常见方法:
方法 | 解释 |
---|---|
boolean add(E e) | 添加元素,但重复元素不会被添加成功 |
void clear() | 清空集合 |
boolean contains(Object o) | 判断 o 是否在集合中 |
Iterator 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 c) | 将集合c中的元素添加到set中,可以达到去重的效果 |
注意:
TreeSet和HashSet的区别:
Set底层结构 | TreeSet | HashSet |
---|---|---|
底层结构 | 红黑树 | 哈希桶 |
插入/删除/查找时间 复杂度 | O(logN) | O(1) |
是否有序 | 关于Key有序 | 不一定有序 |
线程安全 | 不安全 | 不安全 |
插入/删除/查找区别 | 按照红黑树的特性来进行插入和删除 | 1.先计算key哈希地址 2. 然后进行 插入和删除 |
比较与覆写 | key必须能够比较,否则会抛出 ClassCastException异常 | 自定义类型需要覆写equals和 hashCode方法 |
应用场景 | 需要Key有序场景下 | Key是否有序不关心,需要更高的 时间性能 |
public class Main {
public static void main(String[] args) {
HashSet<String> hashSet = new HashSet<>();
//add(key),如果不存在,则插入并返回true
//key为空,报空指针异常
boolean insert = hashSet.add("咖啡");
hashSet.add("拿铁");
//contains(key),如果key存在,返回true
boolean cont = hashSet.contains("咖啡");
//remove(key),如果key存在删除返回true
//key为空,报空指针异常
boolean del = hashSet.remove("咖啡");
//使用迭代器遍历
Iterator<String> it = hashSet.iterator();
while(it.hasNext()){
System.out.println(it.next()+" ");
//程序运行,输出 拿铁
//咖啡 已被删除了
}
}
}
只出现一次的数字
宝石与石头
坏键盘打字
前K个高频单词
复制带随机指针的链表