⭐ 作者:小胡_不糊涂
作者主页:小胡_不糊涂的个人主页
收录专栏:浅谈数据结构
持续更文,关注博主少走弯路,谢谢大家支持
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
下图就是一个二叉搜索树:
进行插入操作:
1. 树为空树
2. 如果树不是空树,按照查找逻辑确定插入位置,插入新结点
进行删除操作:
设待删除结点为 cur, 待删除结点的双亲结点为 parent
public class BinarySearchTree {
public static class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val){
this.val=val;
}
}
public TreeNode root;
/**
* 查找元素
* 最好情况:完全二叉树 O(logN)
* 最坏情况: 单分支的树 O(N)
* @param key
* @return
*/
public boolean search(int key){
TreeNode cur=root;
while(cur!=null){
if(cur.val==key){
return true;
}else if(cur.val>key){
cur=cur.left;
}else{
cur=cur.right;
}
}
return false;
}
//插入元素
public boolean insert(int key){
if(root==null){
root=new TreeNode(key);
return true;
}
TreeNode cur=root;
TreeNode parent=null;
while(cur!=null){
if(cur.val>key){
parent=cur;
cur=cur.left;
}else if(cur.val<key){
parent=cur;
cur=cur.right;
}else{
return false;//树中不能有相等元素
}
}
//若cur=null,判断key与父亲节点的大小
if(parent.val>key){
parent.left=new TreeNode(key);
}
if(parent.val<key){
parent.right=new TreeNode(key);
}
return true;
}
//删除元素
public void remove(int key){
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;
}
if(cur==parent.left){
parent.left=cur.right;
}
if(cur==parent.right){
parent.right=cur.right;
}
}
//第二种情况
if(cur.right == null) {
if(cur == root) {
root = cur.left;
}else if(cur == parent.left) {
parent.left = cur.left;
}else {
parent.right = cur.left;
}
}
//第三种情况
if(cur.left!=null && cur.right!=null){
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;
}
}
}
}
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log2(N)
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2
Map 和 Set 是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。 以前常见的搜索方式有:
上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作了,而现实中的查找比如:
一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以模型会有两种:
Map是一个接口类,该类没有继承自Collection,该类中存储的是
Map.Entry
方法 | 解释 |
---|---|
K getKey() | 返回 entry 中的 key |
V getValue() | 返回 entry 中的 value |
V setValue(V value) | 将键值对中的value替换为指定value |
Map.Entry
方法 | 解释 |
---|---|
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 |
boolean containsValue(Object value) | 判断是否包含 value |
注:
Map底层结构 | TreeMap | HashMap |
---|---|---|
底层结构 | 红黑树 | 哈希桶 |
插入/删除/查找时间复杂度 | O(log2(N)) | O(1) |
是否有序 | 关于Key有序 | 无序 |
线程安全 | 不安全 | 不安全 |
插入/删除/查找区别 | 需要进行元素比较 | 通过哈希函数计算哈希地址 |
比较与覆写 | key必须能够比较,否则会抛出ClassCastException异常 | 自定义类型需要覆写equals和hashCode方法 |
应用场景 | 需要Key有序场景下 | Key是否有序不关心,需要更高的时间性能 |
public static void TestMap(){
Map<String, String> m = new TreeMap<>();
// put(key, value):插入key-value的键值对
// 如果key不存在,会将key-value的键值对插入到map中,返回null
m.put("林冲", "豹子头");
m.put("鲁智深", "花和尚");
m.put("武松", "行者");
m.put("宋江", "及时雨");
String str = m.put("李逵", "黑旋风");
System.out.println(m.size());//5
System.out.println(m);
//{宋江=及时雨, 李逵=黑旋风, 林冲=豹子头, 武松=行者, 鲁智深=花和尚}
// put(key,value): 注意key不能为空,但是value可以为空
// key如果为空,会抛出空指针异常
//m.put(null, "花名");
str = m.put("无名", null);
System.out.println(m.size());//6
// put(key, value):
// 如果key存在,会使用value替换原来key所对应的value,返回旧value
str = m.put("李逵", "铁牛");
// get(key): 返回key所对应的value
// 如果key存在,返回key所对应的value
// 如果key不存在,返回null
System.out.println(m.get("鲁智深"));//花和尚
System.out.println(m.get("史进"));//null
//GetOrDefault(): 如果key存在,返回与key所对应的value,如果key不存在,返回一个默认值
System.out.println(m.getOrDefault("李逵", "铁牛"));//铁牛
System.out.println(m.getOrDefault("史进", "九纹龙"));//九纹龙
System.out.println(m.size());//6
//containKey(key):检测key是否包含在Map中,时间复杂度:O(logN)
// 按照红黑树的性质来进行查找
// 找到返回true,否则返回false
System.out.println(m.containsKey("林冲"));//true
System.out.println(m.containsKey("史进"));//false
// containValue(value): 检测value是否包含在Map中,时间复杂度: O(N)
// 找到返回true,否则返回false
System.out.println(m.containsValue("豹子头"));//true
System.out.println(m.containsValue("九纹龙"));//false
// 打印所有的key
// keySet是将map中的key防止在Set中返回的
for(String s : m.keySet()){
System.out.print(s + " ");//宋江 无名 李逵 林冲 武松 鲁智深
}
System.out.println();
// 打印所有的value
// values()是将map中的value放在collect的一个集合中返回的
for(String s : m.values()){
System.out.print(s + " ");//及时雨 null 铁牛 豹子头 行者 花和尚
}
System.out.println();
// 打印所有的键值对
// entrySet(): 将Map中的键值对放在Set中返回了
for(Map.Entry<String, String> entry : m.entrySet()){
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
System.out.println();
//宋江--->及时雨
//无名--->null
//李逵--->铁牛
//林冲--->豹子头
//武松--->行者
//鲁智深--->花和尚
}
Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key。
方法 | 解释 |
---|---|
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 extendsE> c) | 将集合c中的元素添加到set中,可以达到去重的效果 |
注意:
Set底层结构 | TreeSet | HashSet |
---|---|---|
底层结构 | 红黑树 | 哈希桶 |
插入/删除/查找时间复杂度 | O(log2(N) | O(1) |
是否有序 | 关于Key有序 | 不一定有序 |
线程安全 | 不安全 | 不安全 |
插入/删除/查找区别 | 按照红黑树的特性来进行插入和删除 | 1. 先计算key哈希地址 2. 然后进行 |
插入和删除 | ||
比较与覆写 | key必须能够比较,否则会抛出ClassCastException异常 | 自定义类型需要覆写equals和hashCode方法 |
应用场景需要 | Key有序场景 | Key是否有序不关心,需要更高的时间性能 |
public static void TestSet(){
Set<String> s = new TreeSet<>();
// add(key): 如果key不存在,则插入,返回ture
// 如果key存在,返回false
boolean isIn = s.add("apple");
s.add("orange");
s.add("peach");
s.add("banana");
System.out.println(s.size());//4
System.out.println(s);//[apple, banana, orange, peach]
isIn = s.add("apple");
// add(key): key如果是空,抛出空指针异常
//s.add(null);
// contains(key): 如果key存在,返回true,否则返回false
System.out.println(s.contains("apple"));//true
System.out.println(s.contains("watermelen"));//false
// remove(key): key存在,删除成功返回true
// key不存在,删除失败返回false
// key为空,抛出空指针异常
s.remove("apple");
System.out.println(s);//[banana, orange, peach]
s.remove("watermelen");
System.out.println(s);//[banana, orange, peach]
Iterator<String> it = s.iterator();
while(it.hasNext()){
System.out.print(it.next() + " ");
}
System.out.println();
//banana orange peach
}