二叉搜索树,Map,Set
- 1. 二叉搜索树
- 2. 二叉搜索树的模拟实现
- 1.1 插入操作
- 1.2 查找操作
- 1.3 删除操作
- 3. 二叉搜索树时间复杂度分析
- 4. TreeMap和TreeSet
- 5. Map
- 5.1 Map的常用方法
- 5.2 Map.Entry<K,V>
- 6. Set
- 6.1 Set的常用方法
- LeetCode刷题
- 1. 二叉搜索树与双向链表
1. 二叉搜索树
二叉搜索树是一棵特殊的树,它是“有序的”,因此又被叫做二叉排序树,它具有以下的性质:
- 根节点左子树的任意元素都小于根节点的元素
- 根节点右子树的任意元素都大于根节点的元素
- 上述两条性质对于该树的任意子树都成立
2. 二叉搜索树的模拟实现
待实现代码:
public class BinarySearchTree {
static class TreeNode {
public int key;
public TreeNode left;
public TreeNode right;
TreeNode(int key) {
this.key = key;
}
}
public TreeNode root;
/**
* 插入一个元素
* @param key
*/
public boolean insert(int key) {
return true;
}
//查找key是否存在
public TreeNode search(int key) {
return null;
}
//删除key的值
public boolean remove(int key) {
return false;
}
}
1.1 插入操作
插入一个新节点,首先要遍历原树,找到一个合适的位置去插入,而插入的节点会成为新树的叶子节点
具体实现如下:
public boolean insert(int key) {
if(root == null) {
root = new TreeNode(key);
return true;
}
TreeNode node = new TreeNode(key);
TreeNode cur = root;
TreeNode curParent = null;
//找插入新节点的合适位置
while(cur != null) {
curParent = cur;
if(cur.key < key) {
cur = cur.right;
}else if(cur.key > key) {
cur = cur.left;
}else {
return false;
}
}
//进行插入新节点操作
if(curParent.key < key) {
curParent.right = node;
}else {
curParent.left = node;
}
return true;
}
1.2 查找操作
public TreeNode search(int key) {
TreeNode cur = root;
while(cur != null) {
if(cur.key < key) {
cur = cur.right;
}else if(cur.key > key) {
cur = cur.left;
}else {
return cur;
}
}
return null;
}
1.3 删除操作
二叉搜索树的删除操作是三个操作里最难的,难在需要考虑的情况较多,需要不断用测试用例去调试代码,可以用下面这个链接去验证代码是否还有遗漏的情况
题目链接:NC297 删除一个二叉搜索树中的节点
实现代码:
public boolean remove(int key) {
TreeNode cur = root;
TreeNode curParent = root;
//找到删除节点位置
while(cur != null) {
if(cur.key < key) {
curParent = cur;
cur = cur.right;
}else if(cur.key > key) {
curParent = cur;
cur = cur.left;
}else {
break;
}
}
if(cur == null) {
return true;//节点不存在
}
TreeNode del = cur;//待删除节点
TreeNode delParent = curParent;//待删除节点的父亲节点
//找到待删除节点左子树的最大值(即左子树最右边的元素),用来替换法1待删除节点值
if(del.left == null) {//待删除节点无左子树
if(del.key > delParent.key) {
delParent.right = del.right;
}else if(del.key < delParent.key){
delParent.left = del.right;
}else {
//此时,待删除节点为根节点
root = root.right;
}
}else {
//找到待删除节点左子树的最大值(即左子树最右边的元素),用来替换待删除节点值
curParent = cur;
cur = cur.left;
while(cur.right != null) {
curParent = cur;
cur = cur.right;
}
del.key = cur.key;
if(curParent == del) {
//此时,待删除节点的左子树根节点无右子树
curParent.left = cur.left;
}else {
curParent.right = cur.left;
}
}
return true;
}
3. 二叉搜索树时间复杂度分析
- 在最优情况下,即二叉搜索树是一棵完全二叉树,时间复杂度是O(log2N)
- 在最坏情况下,即二叉搜索树是一棵单支二叉树,时间复杂度是O(N)
最优情况:
最坏情况:
4. TreeMap和TreeSet
TreeMap和TreeSet底层都是一棵红黑树,而红黑树是一棵近似平衡的二叉搜索树。TreeMap是Map接口的一个实现类,TreeSet是Set接口的一个实现类
5. Map
Map是一个接口,其实现需要依靠TreeMap类或者HashMap类。Map里存储的是<K,V>结构的键值对,并且K一定是唯一的,不能重复。这就类比于一个集合,这个集合里放的都是具有两个元素的集合,例如:{{“abc”, 2}, {“cde”, 3}}
5.1 Map的常用方法
- V put (K key, V value)
用于往map中存放键值对
演示1:
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("fbc",2);
map.put("cde",3);
map.put("abc",5);
map.put("grh",8);
System.out.println(map);//Map类可以直接输出
}
}
输出结果:
演示2:
map类存储的key是不能重复的,但往map里put同一个key,是可以的,只是在key里依然只会保存一个key,只是key对应的value会发生更新
- 如果key不存在,会将key-value的键值对插入到map中,返回null
- 如果key存在,会使用value替换原来key所对应的value,返回旧value
- put(key,value): 注意key不能为空,但是value可以为空。 key如果为空,会抛出空指针异常
- V get(Object key)
用于得到key对应的value
演示:
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("fbc",2);
map.put("cde",3);
map.put("abc",5);
map.put("grh",8);
Integer val = map.get("cde");
System.out.println(val);
}
}
输出结果为:3
注意: 该方法接收返回值时,建议使用包装类而不是基本数据类型,因为使用基本数据类型会发生自动拆箱,会调用intValue方法,假如该key不存在,就会返回null,这时如果进行拆箱的话,就会出现空指针异常
3. V getOrDefault(Object key, V defaltValue)
该方法与get方法的不同之处在于有一个默认值,如果key存在,则返回key对应的value值;如果key不存在,就会返回这个默认值
演示:
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("fbc",2);
map.put("cde",3);
map.put("abc",5);
map.put("grh",8);
Integer val = map.getOrDefault("cde2",-1);
System.out.println(val);
}
}
输出结果为:-1
- V remove(Object key)
删除key对应的映射关系
演示:
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("fbc",2);
map.put("cde",3);
map.put("abc",5);
map.put("grh",8);
System.out.println("删除前:" + map);
map.remove("abc");
System.out.println("删除后:" +map);
}
}
输出结果:
- Set<K> keySet()
返回所有的key的不重复集合,keySet()是将map中的key放在set中返回的
演示:
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("fbc",2);
map.put("cde",3);
map.put("abc",5);
map.put("grh",8);
// 打印所有的key
Set<String> set = map.keySet();
System.out.println(set);
for(String s : set) {
System.out.print(s + " ");
}
}
}
输出结果:
- Collection<V> values()
返回所有value的可重复集合,values()是将map中的value放在collect的一个集合中返回的
演示:
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("fbc",2);
map.put("cde",3);
map.put("abc",5);
map.put("grh",8);
// 打印所有的value
Collection<Integer> collection = map.values();
System.out.println(collection);
for(Integer x: collection) {
System.out.print(x + " ");
}
}
}
输出结果:
- boolean containsKey(Object key)
判断是否包含key
演示:
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("fbc",2);
map.put("cde",3);
map.put("abc",5);
map.put("grh",8);
boolean flg1 = map.containsKey("abc");
boolean flg2 = map.containsKey("cba");
System.out.println(flg1);
System.out.println(flg2);
}
}
输出结果:
- boolean containsValue(Object value)
判断是否包含value
演示:
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("fbc",2);
map.put("cde",3);
map.put("abc",5);
map.put("grh",8);
boolean flg1 = map.containsValue(1);
boolean flg2 = map.containsValue(3);
System.out.println(flg1);
System.out.println(flg2);
}
}
输出结果:
- Set<Map.Entry<K, V>> entrySet()
返回所有的 key-value 映射关系,entrySet()将Map中的键值对放在Set中返回了
注意:
- Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入
- 在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常,因为插入键值对时,会在二叉搜索树里进行比较(因此插入的key元素类型要可比,或者在key类中实现了比较方式),将新节点插入到合适位置,而value可以为空。但是HashMap的key和value都可以为空,因为插入时不涉及比较。因此,输出时,如果是TreeMap实现的,则是有序的;若是HashMap实现的,则是无序的
5.2 Map.Entry<K,V>
Map.Entry<K, V> 是Map内部实现的用来存放<key, value>键值对映射关系的内部类,该内部类中主要提供了<key, value>的获取,value的设置以及Key的比较方式
-
K getKey()
返回entry中的key -
V getValue()
返回entry中的value -
V setValue(V value)
将键值对中的value值替换成指定value
通过该方法转化,既可以获取key,也可以获取value;而Map中的get方法只能获取key所对应的value值
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("fbc",2);
map.put("cde",3);
map.put("abc",5);
map.put("grh",8);
打印所有的键值对
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
System.out.println(entrySet);
System.out.println("========");
for (Map.Entry<String,Integer> entry:entrySet) {
System.out.println("key:" + entry.getKey()+" val:" + entry.getValue());
}
}
}
输出结果:
6. Set
Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key,且Key唯一。Set类似于一个集合,具有互异性,最大的作用就是去重。Set的实现依靠TreeSet、HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础上维护了一个双向链表来记录元素的插入次序。
6.1 Set的常用方法
- boolean add(E e)
用来给集合添加元素,但不会添加重复元素
演示1:
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(4);
set.add(7);
set.add(1);
set.add(3);
System.out.println(set);
}
输出结果:
演示2:
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(1);
set.add(7);
set.add(1);
set.add(3);
System.out.println(set);
}
输出结果
和Map类似,Set也可以add集合中已有的元素,但不会将其加入集合中,从而达到去重的效果
- void clear()
清空集合中的元素
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(4);
set.add(7);
set.add(1);
set.add(3);
System.out.println(set);
set.clear();
System.out.println(set);
}
输出结果:
- boolean contains(Object o)
判断o是否在集合中
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(4);
set.add(7);
set.add(1);
set.add(3);
System.out.println(set);
System.out.println(set.contains(4));
System.out.println(set.contains(5));
}
输出结果:
- Iterator iterator()
返回迭代器
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(4);
set.add(7);
set.add(1);
set.add(3);
Iterator<Integer> it = set.iterator();
while(it.hasNext()) {
System.out.print(it.next()+" ");
}
}
输出结果:
- boolean remove(Object o)
删除集合中的o
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(4);
set.add(7);
set.add(1);
set.add(3);
System.out.println(set);
set.remove(4);
System.out.println(set);
}
输出结果:
- int size()
返回set中的元素个数
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(4);
set.add(7);
set.add(1);
set.add(3);
System.out.println(set.size());
}
- boolean isEmpty()
判断集合set是否为空,为空则返回true,反之返回false
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
System.out.println(set.isEmpty());
set.add(4);
set.add(7);
set.add(1);
set.add(3);
System.out.println(set.isEmpty());
}
输出结果:
- Object[] toArray()
将set中的元素转换为数组返回
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(4);
set.add(7);
set.add(1);
set.add(3);
System.out.println(Arrays.toString(set.toArray()));
System.out.println("========");
Object[] arr = set.toArray();
for (Object o : arr) {
System.out.print(o + " ");
}
}
输出结果:
- boolean containsAll(Collection<?> c)
检查集合c中的元素是否在set中全部存在,是则返回true,否则返回false
public static void main(String[] args) {
Collection<Integer> c = new ArrayList<>();
c.add(1);
c.add(2);
c.add(3);
Set<Integer> set = new TreeSet<>();
set.add(1);
set.add(2);
System.out.println(set.containsAll(c));
set.add(3);
System.out.println(set.containsAll(c));
}
输出结果:
- boolean addAll(Collection<?extends E> c)
将集合c中的元素添加到set中,可以达到去重的效果
public static void main(String[] args) {
Collection<Integer> c = new ArrayList<>();
c.add(1);
c.add(2);
c.add(1);
c.add(3);
c.add(2);
c.add(4);
System.out.println(c);
Set<Integer> set = new TreeSet<>();
set.addAll(c);
System.out.println(set);
}
输出结果:
注意:
- Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
- TreeSet中不能插入null的key,若插入null,则会抛出空指针异常;而HashSet可以
LeetCode刷题
1. 二叉搜索树与双向链表
题目链接:将二叉搜索树转化为排序的双向链表
解题思路:
- 对该二叉搜索树采取中序遍历,先找到二叉搜索树最左边的节点
- 定义两个引用prev和head,prev用来引用当前pRootOfTree的前一个节点,head始终引用原二叉搜索树最左边的节点,用于返回转化成的双向链表
代码如下:
public class Solution {
public TreeNode prev;
public TreeNode head;
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null) {
return null;
}
Convert(pRootOfTree.left);
if (prev == null) {
prev = pRootOfTree;
head = pRootOfTree;
} else{
prev.right = pRootOfTree;
pRootOfTree.left = prev;
prev = pRootOfTree;
}
Convert(pRootOfTree.right);
return head;
}
}