Java笔记: TreeMap和TreeSet

📒数据结构专栏简介: 内容主要以介绍数据结构为主, 并以一些题目辅以应用说明, 希望同学们看了之后能有所收获🉐

👷🏻‍♂️作者也是一名刚上路的小白coder🛵, 欢迎各位评论, 批评指正或学习交流👁‍🗨~~


Map 和 Set

在介绍TreeMapTreeSet前, 首先要简单介绍一下MapSet这两个接口, 因为TreeMapTreeSet分别实现了这两个接口, 知其然知其所以然嘛, 让我们了解下MapSet究竟是"何方神圣", 上图 :

关系图

可以看到, MapSet是在不同包名下的:

Mapjava.util包下的一个接口, 它的实现类主要是存储<K, V>键值对, 且其中的K值是唯一的;

Setjava.lang包下的一个接口, 是 Collection的一个子接口, 存储的是K值, K值也不能重复


Map简介

Map存储<K, V>键值对, 且其中的K值是唯一的

<K, V>实际上是 Key - Value 模型, Key 和 Value 之间可以存在映射关系, 如: A班有50个学生, 键值对则可以写成 <A, 50>

而相对 Key - Value 模型的则是纯 Key 模型, 该模型只存储Key, 如: A班中的每个学生id或者名字


Map的一些注意事项

  • Map是一个接口, 不能直接实例化, 若要使用需要实例化实现Map的子类, 如TreeMap, HashMap
  • Map存储<K, V>键值对, 且其中的Key值是唯一的, 不能重复, 但Value是可以重复的
  • Map中的<K, V>键值对中的Key不能直接修改, Value 可以进行修改, Key只能先删除再重新插入
  • Map中的Key可以分离出来存储在Set
  • Map中的Value可以分离出来存储在Collection中的子集合

Map的一些方法

方法简述
void clear()从此映射中移除所有映射关系
boolean containsKey(Object o)如果此映射包含指定键的映射关系,则返回 true
boolean containsValue(Object o)如果此映射将一个或多个键映射到指定值,则返回 true
Set<Map.Entry<K,V>>entrySet()返回此映射中包含的映射关系的 Set视图
boolean equals(Object o)比较指定的对象与此映射是否相等
V get(Object key)返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
boolean isEmpty()如果此映射未包含键-值映射关系,则返回 true
Set keySet()返回此映射中包含的键的 Set 视图
V put(K key, V value)将指定的值与此映射中的指定键关联
V remove(Object key)如果存在一个键的映射关系,则将其从此映射中移除
int size()返回此映射中的键-值映射关系数
Collection values()返回此映射中包含的值的 Collection

Entry<K,V>是一个Map内部的interface, Entry<K,V>提供了<Key, Value>获取方法, Key的获取, Value的获取和修改, Map.entrySet()用于遍历

 /**
     * A map entry (key-value pair).  The <tt>Map.entrySet</tt> method returns
     * a collection-view of the map, whose elements are of this class.  The
     * <i>only</i> way to obtain a reference to a map entry is from the
     * iterator of this collection-view. 
     */

Set简介

Set存储Key值, 且其中的K值是唯一的, 存储的是纯Key模型, 是一个不包含重复元素的 collection


Set的注意事项

  • Set中不能插入null

  • SetCollection下的一个子接口, 其实现类也是一个collection, 因此可以在实例化其他实现Collection接口的类时可以通过Set构造对象, 如:

    Set<String> set = new HashSet<>();
    set.add("a");
    set.add("b");
    set.add("c");
    List<String> list = new ArrayList<>(set);//通过set实例化一个list
    
  • Set中的Key不能重复, 且存储的Key不能直接修改, 只能先删除再插入新的Key

  • Set的底层实际上是Map, 插入时存储的是一个Key和一个默认的Object对象

  • Set的最大的功能是创建了一个元素不会重复的collection

Set的一些方法

方法简述
boolean add(E e)如果set中尚未存在指定的元素,则添加此元素
void clear()移除此set中的所有元素
boolean contains(Object o)如果``set包含指定的元素,则返回 true`
boolean equals(Object o)比较指定对象与此set的相等性
boolean isEmpty()如果set不包含元素,则返回 true
boolean remove(Object o)如果set中存在指定的元素,则将其移除
int size()返回set中的元素数(其容量)
Object[] toArray()返回一个包含set中所有元素的Object数组
T[] toArray(T[] a)返回一个包含此set中所有元素的数组, 返回数组的运行时类型是指定数组的类型

到这里可能会有一个疑问: 既然 Map 和 Set 中的K值都不能重复, Map 还能存储具有映射关系(K与V对应)的键值对, 为什么还要设置一个Set呢?

Map是不同于Collection的一个独立接口, 有时候我们需要用Map里的数据创建另外的结构, 就如我需要使用Map里的数据构建一个Heap(堆), 那么就可以先将Map中的Key存储到Set中, 再通过Set建堆


有了前面的铺垫, 我们终于可以看看今天的"主角" — TreeMapTreeSet

TreeMap

TreeMap的存储

源码注释说明: TreeMap的底层结构是一棵红黑树, 元素在TreeMap中是关于Key有序存储的, 且元素间是可以比较的

/**
 * A Red-Black tree based {@link NavigableMap} implementation.
 * The map is sorted according to the {@linkplain Comparable natural
 * ordering} of its keys, or by a {@link Comparator} provided at map
 * creation time, depending on which constructor is used.
 */

红黑树是一棵复杂的二叉搜索树(或称二叉查找树), 什么是二叉搜索树呢?

我们规定结点中存储着可比较的元素, 树中的元素不会重复, 将任一结点视作根结点, 存储元素比根结点中元素小的结点为根结点的左子树, 则反之为右子树, 如下图构建一棵树:

二叉搜索树

这样就能获得一棵二叉搜索树, 红黑树的实现比较杂, 对红黑树感兴趣的小伙伴可以自行搜索, 这里仅模仿二叉搜索树的实现


二叉搜索树的模仿实现

基本代码如下:

public class BinarySearchTree {

    static class TreeNode {
        public int key;
        public TreeNode left;
        public TreeNode right;

        TreeNode(int key) {
            this.key = key;
        }
    }

    public TreeNode root;
	//插入
    public boolean insert(int key);
    //查找
    public TreeNode search(int key);
    //删除
    public boolean remove(int key);
}

建议初学者动手画出代码运行流程的二叉树图, 能更快的上手和理解

查找

查找部分代码应该是最容易的, 只需利用二叉搜索树左子树key比父结点中的小, 右子树的key比父结点中的大的性质就能完成

    //查找key是否存在
    public TreeNode search(int key) {
        TreeNode cur = root;
        while (cur != null) {
            if (key == cur.key) return cur;
            if (key < cur.key) {
                cur = cur.left;
            } else {
                cur = cur.right;
            }
        }
        return null;
    }
插入

从上述的二叉搜索树创建图中我们能发现: 每次将key插入树中时, 插入的位置一定是为null位置, 成为叶结点

	 //插入key
	public boolean insert(int key) {
        if (root == null) {
            root = new TreeNode(key);
            return true;
        }
        TreeNode cur = root;
        TreeNode prev = root;
        while (cur != null) {
            if (key == cur.key) return false;
            prev = cur;
            if (key < cur.key) {
                cur = cur.left;
            } else {
                cur = cur.right;
            }
        }
        if (key < prev.key) {
            prev.left = new TreeNode(key);
        } else {
            prev.right = new TreeNode(key);
        }
        return true;
    }
删除

删除部分是比较麻烦的一部分代码, 因为要在删除后保证树还是一棵二叉搜索树

	 //删除key
    public boolean remove(int key) {
        //跟查找部分差不多, 先找到key与其父结点
        TreeNode cur = root;
        TreeNode prev = null;
        while (cur != null) {
            if (key == cur.key) break;
            prev = cur;
            if (key < cur.key) {
                cur = cur.left;
            } else {
                cur = cur.right;
            }
        }
        if (cur == null) {
            return false;
        }
        return remove(prev, cur);
    }

	//删除时要注意的是 root, cur 和 cur的子树的情况
    private boolean remove(TreeNode parent, TreeNode cur) {
        //cur左子树为空时
        if (cur.left == null) {
            //若cur为root 则可以直接用右子树将替换cur
            if (cur == root) {
                root = cur.right;
   			//若不为root, 则可能为父结点的左子树或右子树
            } else if (cur == parent.left) {
                parent.left = cur.right;
            } else {
                parent.right = cur.right;
            }
        //cur右子树为空时, 情况与上述相似
        } 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;
            //选取右子树为target, 从target的左子树开始循环, 直至target的左子树为空, 此时target便是要找的"替罪羊"
            while (target.left != null) {
                targetParent = cur;
                target = target.left;
            }
            //交换cur和traget, 为其"替罪"
            cur.key = target.key;
            //当要也要考虑target的左子树一开始就为空的情况
            if (target != targetParent.right) {
                targetParent.left = target.right;
            } else {
                targetParent.right = target.right;
            }
        }
        return true;
    }

TreeMap的创建和常用方法

创建

//使用键的自然顺序构造一个新的、空的树映射
	TreeMap<K, V> treeMap = new TreeMap<K,V>();

//构造一个新的、空的树映射,该映射根据给定比较器进行排序
  // 对应构造方法: 
	TreeMap(Comparator<? super K> comparator) ;
//创建
	TreeMap<K, V> treeMap = new TreeMap<K,V>(new Comparator<K>() {
        @Override
        public int compare(K o1, K o2) {
            return 0;
        }
    });


//构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序进行排序
// 对应构造方法:
	TreeMap(Map<? extends K,? extends V> m) ;
//如利用已有Hashtable创建:
	Hashtable<K, V> table;//假设table完成初始化
	TreeMap<K, V> treeMap = new TreeMap<K,V>(table);


//构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射
// 对应构造方法: 
	TreeMap(SortedMap<K,? extends V> m) ;
//如利用已有HashMap创建:
	HashMap<K, V> map;//假设map完成初始化
	TreeMap<K, V> treeMap = new TreeMap<K,V>(map);

常用方法

方法简述
boolean containsKey(Object key)如果此映射包含指定键的映射关系,则返回 true
boolean containsValue(Object value)如果此映射为指定值映射一个或多个键,则返回 true
Set<Map.Entry<K,V>> entrySet()返回此映射中包含的映射关系的 Set视图
V get(Object key)返回指定键所映射的值,如果对于该键而言,此映射不包含任何映射关系,则返回 null
Set keySet()返回此映射包含的键的 Set 视图
V put(K key, V value)将指定值与此映射中的指定键进行关联
V remove(Object key)如果此 TreeMap 中存在该键的映射关系,则将其删除
int size()返回此映射中的键-值映射关系数
SortedMap<K,V> subMap(K fromKey, K toKey)返回此映射的部分视图,其键值的范围从 fromKey(包括)到 toKey(不包括)
SortedMap<K,V> tailMap(K fromKey)返回此映射的部分视图,其键大于等于 fromKey

TreeSet

TreeSet的存储

TreeSet的底层是基于TreeMap实现的

/**
 * A {@link NavigableSet} implementation based on a {@link TreeMap}.
 * The elements are ordered using their {@linkplain Comparable natural
 * ordering}, or by a {@link Comparator} provided at set creation
 * time, depending on which constructor is used.
 */

TreeSet的创建和常用方法

创建

TreeSet的实例化与TreeMap大同小异, 这里就只介绍构造方法

//构造一个新的空 set,该 set 根据其元素的自然顺序进行排序
	TreeSet();

//构造一个包含指定 collection 元素的新 TreeSet,它按照其元素的自然顺序进行排序
	TreeSet(Collection<? extends E> c);

//构造一个新的空 TreeSet,它根据指定比较器进行排序
	TreeSet(Comparator<? super E> comparator);

//构造一个与指定有序 set 具有相同映射关系和相同排序的新 TreeSet
	TreeSet(SortedSet<E> s);

常用方法

方法简述
boolean add(E e)将指定的元素添加到此 set(如果该元素尚未存在于 set 中)
void clear()移除此set中的所有元素
boolean clone()如果此 set 包含指定的元素,则返回 true
boolean isEmpty()如果此 set 不包含任何元素,则返回 true
boolean remove()将指定的元素从 set 中移除(如果该元素存在于此 set 中)
int size()返回 set 中的元素数(set 的容量)
SortedSet subSet(E fromElement, E toElement)返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。
SortedSet tailSet(E fromElement)返回此 set 的部分视图,其元素大于等于 fromElement


文章到此结束, 感谢耐心阅读😸

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值