文本目录:
❄️一、搜索树:
☑ 1、概念:
☑ 2、操作-插入:
代码:
☑ 3、操作-查看:
代码:
☑ 4、操作-删除:
代码:
☑ 5、性能分析:
❄️二、搜索:
☞ 1、概念和场景:
☞ 2、模型:
❄️ 三、Map的使用:
▶ 1、关于Map的说明:
▶ 2、Map常用的方法:
▶ 3、Map使用时候的注意:
❄️四、Set的使用:
▶ 1、Set常用的方法:
▶ 2、Set使用时候的注意:
❄️总结:
我们呢请出我们的一个老朋友:
对于这张表呢,我们就只剩下关于Set和Map的这个接口了,而对于这连个接口呢是和我们搜索相关的一个接口,那么在了解Set和Map之前呢,我们来介绍一个新的二叉树——二叉搜索树
❄️一、搜索树:
☑ 1、概念:
二叉搜索树又称二叉排序树,它或者是空树,如果不是空树就要具备以下的性质:
1、如果它的左子树不为空的话,其所有左子树的节点的值要比根节点小
2、如果它的右子树不为空的话,其所有右子树的节点的值要比根节点大
3、它的左右子树都为二叉搜索树
我们来看一个例子:
这个就是一个二叉搜索树。
我们接下来自实现一下关于二叉搜索树的 插入、查找、删除操作。
在执行操作之前呢,我们先把其准备工作准备好,我们要设置我们二叉搜索树的节点和其根节点:
public class BinarySearchTree {
static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
public TreeNode root;
}
☑ 2、操作-插入:
我们的插入呢就是非常的简单了,我们要进行以下操作:
1、判断是否是空。如果是空树,第一个节点就是根结点。
2、不为空的时候,我们定义一个 parent 和 cur 临时变量。parent 用来存储 cur 的根节点,cur 用 来遍历二叉搜索树,来寻找要插入的地方。
3、每次走 cur 这前呢,执行 parent = cur ,为了把 cur 的父亲节点记录下来
4、我们要判断每一个 cur.val 和 我们插入的 val 值大小:
如果 cur.val < val 执行 cur = cur.right
如果 cur.val > val 执行 cur = cur.left
这个操作是循环的,直至我们的 cur == null 的时候呢,就跳出循环。
这里要注意:我们的二叉搜索树是不能存储两个相同的节点的。
5、这个时候我们的 parent 这个节点就是 cur 的父亲节点,之后再次比较 parent.val 和 val 的值
如果 parent.val > val 执行 parent.left = val这个值的节点
如果 parent.val < val 执行 parent.right = val这个值的节点
代码:
public void insert(int val) {
if (root == null) {
root = new TreeNode(val);
return;
}
TreeNode newNode = new TreeNode(val);
TreeNode parent = null;
TreeNode cur = root;
while (cur != null) {
if (cur.val < val) {
parent = cur;
cur = cur.right;
} else if (cur.val > val) {
parent = cur;
cur = cur.left;
}else {
//不能存在相同的节点
return;
}
}
if (parent.val < val) {
parent.right = newNode;
}else {
parent.left = newNode;
}
}
☑ 3、操作-查看:
这个方法就很简单了,我们只需要进行和要查找的 val 进行比较就可以了
1、定义一个 cur 的临时变量存储 root 用于遍历
2、用 cur.val 和 val 值进行比较
如果 cur.val < val 执行 cur = cur.right
如果 cur.val > val 执行 cur = cur.left
如果 cur.val = val 我们直接返回 cur 这个节点就可以了
我们直接来看代码:
代码:
public TreeNode search(int val) {
if (root == null) {
return null;
}
TreeNode cur = root;
while (cur != null) {
if (cur.val < val) {
cur = cur.right;
} else if (cur.val > val) {
cur = cur.left;
} else {
return cur;
}
}
return null;
}
☑ 4、操作-删除:
删除呢,我们需要先找到我们要删除的节点,这里我们使用 cur 来设为要删除的节点的,用 parent 来记录我们要删除的节点的父亲节点。在我们找到要删除的节点节点之后呢,我们对于删除操作有几种不同的情况,我们来看看:
1、cur.left == null 时
如果:cur == root 执行 root = cur.right
如果:parent.left == cur 执行 parent.left = cur.right
如果:parent.right == cur 执行 parent.right = cur.right
2、cur.right == null 时
如果:cur == root 执行 root = cur.left
如果:parent.left == cur 执行 parent.left = cur.left
如果:parent.right == cur 执行 parent.right = cur.left
3、cur.left != null && cur.right != null
我们先设置 target 存储 cur.right,targetParent = cur
我们这个时候呢有两个方法做到删除操作,
其一:在左树中找到最大值(左树中的最右边的节点)
其二:在右数中找到最小值(右树中的最左边的节点)
先用其中一个,之后呢执行 cur.val = target.val
之后我们还要进行判断
如果:targetParent.left == target 执行 targetParent.left = target.right
如果:targetParent.right == target 执行 targetParent.right = target.right
这里我演示的使用的在左树中找最大值
我们来看看这个整体的代码的编写:
代码:
public void remove(int val) {
if (root == null) {
return;
}
TreeNode parent = null;
TreeNode cur = root;
while(cur != null) {
if (cur.val < val) {
parent = cur;
cur = cur.right;
} else if (cur.val > val) {
parent = cur;
cur = cur.left;
} else {
removeNode(parent,cur);
}
}
}
private void removeNode(TreeNode parent, TreeNode cur) {
if (cur.left == null) {
if (cur == root) {
root = cur.right;
} else if (parent.left == cur) {
parent.left = cur.right;
} else {
parent.right = cur.right;
}
} else if (cur.right == null) {
if (cur == root) {
root = cur.left;
} else if (parent.left == cur) {
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;
if (targetParent.left == target) {
targetParent.left = target.right;
} else {
targetParent.right = target.right;
}
}
}
☑ 5、性能分析:
最好的情况下:二叉树趋于完全二叉树,这样平均比较次数为:logN
最坏的情况下:二叉树为单分支,这样平均比较次数为:N/2
❄️二、搜索:
☞ 1、概念和场景:
我们要介绍的Map 和 Set 呢是一种专门用来搜索的容器或者数据结构,其搜索的效率和具体的实例化子类有关系。
以前的我们的搜索方式有:
1、直接遍历:时间复杂度为O(N),当数据多的时候就会更慢。
2、二分查找:时间复杂度为O(logN),但是搜索的序列必须是有序的。
上面的这些呢,是比较适合进行 静态查找 的,一般不会在查找的时候进行插入和删除操作了。
那么我们有时候就需要在查找的时候进行 插入或者是删除 操作了,这就是 动态查找 了,这次我们要介绍的 Map和Set 就是一种适合 动态查找 的集合容器了。
☞ 2、模型:
一般情况下,我们把要搜索的数据称为 关键字(key) ,和关键字对应的称为 值(value),将其称为 key-value 键值对,所以模型会有两种:
1、key-value 模型,比如:
统计文件中 每个但出现的次数,<单词,单词出现的次数>
2、纯 key 模型,比如:
快速查找某个单词是否在文件中。
我们将要介绍的 Map是key-value 模型,而 Set是纯key 模型。
❄️ 三、Map的使用:
Map的官方文档
▶ 1、关于Map的说明:
对于 Map 是一个接口,该类是没有继承 Collection 这个接口的,该类中存储的是 <K,V>模型,并且这里的 K 一定是唯一的,不能重复。
▶ 2、Map常用的方法:
因为我们的 Map 是一个接口,所以不能实例化对象,所以需要我们使用 TreeMap 和 HashMap 来实例化对象。
我们的 TreeMap 的底层使用的是 红黑树 是我们上面介绍的二叉搜索树的一种,所以我们的才会介绍 二叉搜索树 。而 HashMap 的底层是 哈希桶 ,这个我们后面会介绍 Hash。
V put(K key,V value) | 设置 key 所对应的 value 值 |
我们来看看如何使用的:
这个呢就是我们的Map中的 put 方法,当然这里也可以吧Map替换成 TreeMap 或者 HashMap。
V get(Object key) | 返回传入的 key 所对应的 value 值 |
这里没有我们的 key 值,我们呢应该返回null 但是呢,我们如果对null 进行拆箱操作的话呢,就会出现空指针异常,所以我们这里要使用 Integer 来接收,就不会出现空指异常。
V getOrDefault(Object key,V defaultValue) | 返回 key所对应的 value 值,如果key不存在,返回默认值。 |
V remove(Object key) | 删除 key 所对应的映射关系 |
boolean containsKey(Object key) | 判断是否包含 key |
boolean containsValue(Object value) | 判断是否包含 value |
我们接下来介绍几个特殊的 Map 方法,这些方法中使用到了 Set 这个接口,Set 呢是不能存储相同的数据的这里要注意。
Set<K> keySet() | 返回所有的不重复的 key 的集合,放到Set 集合中 |
Collection<V> values() | 判断是否包含 key |
Set<Map.Entry<K,V>> entrySet() | 返回所有的 key-value 的映射关系 |
我们这里的 Map.Entry 就是我们的搜索树的每一个节点,这里面呢有 getValue()得到 value 值 ,getKey()得到 key 值,setValue(V value) 这个是将其 value 替换为指定的 value 值。
▶ 3、Map使用时候的注意:
1):
Map 是一个接口不能直接实例化对象,如果想要实例化对象呢,我们只能实例化其实例类TreeMap 或者是 HashMap
2):
Map 中存放的键值对的 key 是唯一的不能重复,而 value 是能进行重复的。
3):
在TreeMap 中我们插入对象的时候呢,key 不能为空,否则抛空指针异常。但是我们的 value 可以为空,但是在 HashMap 中呢 都可以为空。
4):
Map 中的 key可以分离出来存放到 Set 中,因为不能重复。
5):
Map 中的 value 也可以分离出来存放到 Collection 中,因为可以出现重复的值,所以不能存放到 Set 中。
6):
当 key 的值出现重复的时候呢,我们的key 映射的 value 值是后出现的值。
7):
我们的 存放的时候呢,我们是根据 key 进行比较来确定存放顺序的,不是根据 value 来确定的存放顺序的。
❄️四、Set的使用:
Set的官方文档
我们的 Set 和 Map 是不同的,我们的 Set 是继承自 Collection 的接口的,并且Set 中只存储 key值。
▶ 1、Set常用的方法:
boolean add(E e) | 添加元素,但是不会添加重复的元素 |
boolean contains(Object o) | 判断 o 是否出现在 Set 集合中 |
boolean remove(Object o) | 删除集合中的 o |
Iterator<E> iterator() | 返回迭代器 |
我们的 Map 是不能使用迭代器的,但是呢我们可以使用Set 来间接使用迭代器遍历Map :
int size() | 返回Set 中的元素个数 |
boolean isEmpty() | 判断set是否为空 |
Object[ ] toArray | 将 set 中的元素转换为数组返回 |
boolean containsAll(Collection<?> c) | 集合 c 中的元素是否在set中全部存在 |
boolaen addAll(Collecton<?> c) | 将集合 c 中的元素存放到set集合中,可以达到去重的效果 |
▶ 2、Set使用时候的注意:
1):
Set 是继承 Collection 的一种接口
2):
Set 只存储了 key,并且key 要唯一
3):
TreeSet 的底层使用的是 Map 来实现的,其使用key 与 Object 的一个默认对象作为键值对插入 Map 中。
4):
Set 最大的功能就是对元素进行去重操作
5):
实现 Set 接口的类有 TreeSet 和 HashSet,还有一个LinkedHashSet,这个是在HashSet 的基础上有一个 双向链表 来记录元素的插入顺序。
❄️总结:
OK,但这里我们的 Set 和 Map 的有关知识的一部分就到这里就结束了,在下一篇博客中会介绍关于 哈希 的一些知识,并且自实现一下 哈希表。让我们尽情期待吧!!!拜拜~~~