Java TreeMultiSet-支持可重复元素的TreeSet

TreeMultiSet

基于TreeMap实现的支持可重复元素的TreeSet
github地址,欢迎star

为什么要开发这个数据结构

搞过java的人应该都知道TreeSet,但是TreeSet是不支持重复元素的。有人会说,那用ArrayList或LinkedList不就可以了吗?
确实,ArrayList或LinkedList天然不去重,可以满足支持重复元素的需求。但是,我不仅需要支持可重复元素,而且需要数据实时保持有序。
这里有人又会说了,有序不很简单么?我把数据插入到ArrayList或LinkedList中之后,调用sort不就完事了吗?
嗯,确实,如果你的列表不经常发生变化(sort的次数非常非常少),那么你使用List+sort当然没问题咯。但是,如果数据是不断发生变化的怎么办呢?
如果数据不断发生变化,而且又需要保证数据实时有序(比如有实时查询最小和最大的需求),如果你使用List+sort的方式这样的时间复杂度非常高:
大概是插入排序的思路,这样的单次插入时间复杂度是O(n),如果有n个元素,则需要O(n2)。因此呢,如果我们要降低时间复杂度,就不能使用List。
然后,接下来又有人说了,我直接用TreeMap似乎可以啊。TreeMap的key是元素,value是key对应元素出现的次数。这样就可以满足插入可重复且有序的需求了。
的的确确,这个确实可以大致满足可重复且有序的需求。但是,我这里举几个使用TreeMap来满足可重复且有序需求的缺点:

  1. 如果我们需要知道可重复元素集合的个数(重复元素算多个),使用TreeMap不能立马且在O(1)时间复杂度之内获取到,
    而需要for循环所有key,然后计算所有value值之和。大致代码如下:
    int size = 0;
    for (Integer num : treeMap.keySet()) {
        size += treeMap.get(num);
    }

这样看起来是不是有点麻烦?获取集合个数我们期望的应该是下面这样这样简洁(TreeMultiSet就能达到,下面会介绍):

    set.size();
  1. 如果我们需要遍历集合中的所有元素,重复元素需要遍历多次(类似list的遍历)。那么使用TreeMap来实现需要使用二重循环,不太直观。如下所示:
    for (Integer num : treeMap.keySet()) {
        int count = treeMap.get(num);
        while ((count--) > 0) {
            // 需要使用循环将num输出count次
            System.out.println(num);
        }
    }

同样,我们期望的应该是一重循环搞定,像下面这样:

    for (Integer num : set) {
        System.out.println(num);
    }
  1. 如果我们需要删除集合中指定个数的某个元素。举个例子,集合[1, 2, 2, 3, 3,3]。我只想删除两个3,TreeMap需要这么做:
    if (treeMap.containsKey(3)) {
        treeMap.put(2, treeMap.get(3) - 2);
    }

同样,我们期望的应该是像下面这样:

    set.remove(3, 2); // 第二个参数代表需要删除的元素的个数
  1. 如果我们需要往集合中添加指定数量的元素。举个例子,集合[1, 2, 2, 2, 4],我们需要添加两个4,TreeMap需要这么做:
    treeMap.put(4, treeMap.getOrDefault(4, 0) + 2);

同样,我们期望的应该像下面这样:

    set.add(4, 2); // 第二个参数代表需要插入的元素的个数

除此之外,TreeMap来实现可重复treeSet的功能还有一些不太优雅的地方,这里就不一一列举了。

因此,基于以上原因,TreeMultiSet诞生了。(不过声明一下:不是重复造轮子,而是站在巨人的肩膀上,TreeMultiSet大部分是参考TreeSet来实现的,
其实都是基于TreeMap,而TreeMap底层是通过红黑树(Red-Black-Tree)来实现的,这也正是TreeSet的remove操作可达到O(logN)时间复杂度的本质原因,有兴趣的同学可以去研究一下

如何使用

gradle

Step 1. Add the JitPack repository in your root build.gradle at the end of repositories:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Step 2. Add the dependency in your app build.gradle:

dependencies {
    implementation 'com.github.yuruiyin:TreeMultiSet:1.0.1'
}

功能列表

功能描述 对应方法
无参构造函数 TreeMultiSet()
带比较器参数的构造函数 TreeMultiSet(Comparator<? super E> comparator)
带集合参数构造函数 TreeMultiSet(Collection<? extends E> c)
带SortedSet参数构造函数 TreeMultiSet(SortedSet<E> s)
返回所有元素(重复元素要next多次)的正向迭代器 Iterator<E> iterator()
返回所有元素(重复元素要next多次)的反向迭代器 Iterator<E> descendingIterator()
返回所有不相同元素的正向迭代器 Iterator<E> diffIterator()
返回所有不相同元素的反向迭代器 Iterator<E> diffDescendingIterator()
返回逆序Set NavigableSet<E> descendingSet()
返回指定头尾元素的连续子集 NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)
返回头部连续子集 NavigableSet<E> headSet(E toElement, boolean inclusive)
返回尾部连续子集 NavigableSet<E> tailSet(E fromElement, boolean inclusive)
返回比较器 Comparator<? super E> comparator()
返回总的元素个数(重复算多个) int size()
返回不同的元素个数 int diffElementSize()
获取第一个元素 E first()
获取最后一个元素 E last()
判断是否包含某个元素 boolean contains(Object o)
清空所有元素 void clear()
添加指定元素(1个) boolean add(E e)
添加指定个数的特定元素 boolean add(E e, int count)
设置指定元素的数量 void setCount(E e, int count)
获取指定元素的个数 int count(E e)
删除1个指定元素 boolean remove(Object e)
删除count个指定元素 boolean remove(E e, int count)
删除所有的指定元素(不同于clear()) boolean removeAll(Object e)
返回比给定元素严格小的最大元素 E lower(E e)
返回小于或等于给定元素的最大元素 E floor(E e)
返回比给定元素严格大的最小元素 E higher(E e)
返回大于或等于给定元素的最小元素 E ceiling(E e)
删除指定count的第一个元素 E pollFirst()
删除1个第一个元素 E pollFirst()
删除所有count的第一个元素 E pollFirstAll()
删除1个最后一个元素 E pollLast()
删除指定count的最后一个元素 E pollLast(int count)
删除所有count的最后一个元素 E pollLastAll()
TreeMultiSet浅拷贝 Object clone()

代码演示

这里举几个觉得常用的一些方法的调用方式(当然也可以直接阅读TreeMultiSet源码, 里头有详细的注释)。

1) 新建一个自定义比较器的TreeMultiSet

    TreeMultiSet<Integer> set = new TreeMultiSet<>((o1, o2) -> o2 - o1); // 传一个从大到小的比较器

2) foreach遍历所有元素(包含重复元素)

    for (Integer num : set) {
        // TODO, 这里可输出类似2, 2, 2, 3这样的序列
    }

3) 获取所有元素(包含重复元素)的正向迭代器

    Iterator<Integer> iterator = set.iterator();
    while (iterator.hasNext()) {
        arr[i++] = iterator.next();
    }

4) 获取不同元素的正向迭代器

    Iterator<Integer> diffIterator = set.diffIterator();
    while (diffIterator.hasNext()) {
        arr[i++] = diffIterator.next();
    }

5) 获取所有元素的总个数(包含重复元素)

    set.size();

6) 获取不同元素的个数

    set.diffElementSize();

7) 获取第一个元素和最后一个元素

    set.first();
    set.last();

8) 添加指定数量的元素

    set.add(2, 3); //往集合里头添加3个2

9) 设置指定元素的数量

    set.setCount(2, 3); //设置集合里头2的个数为3个

10) 获取指定元素的个数

    set.count(2); //获取2在集合中的个数

11) 删除指定数量的元素

    set.remove(2, 3); //删除3个2

12) 删除指定数量的第一个元素

    set.pollFirst(2); //删除2个第一个元素。如集合[1,1,1,2,2,3],执行这行代码之后变成[1,2,2,3]

13) 删除指定数量的最后一个元素

    set.pollLast(1); //删除1个最后一个元素。如集合[1,1,1,2,3,3],执行这行代码之后变成[1,1,1,2,2,3]

单元测试

已经对TreeMultiSet的所有方法(包括构造函数)都进行了单元测试TreeMultiSetTest,测试覆盖率达到100%。

Coverage

各种集合对比

说明,以下列举的复杂度均指时间复杂度。并且以下插入删除操作均指对中间元素的操作。同时,计算LinkedList的插入和删除时间复杂度的时候考虑了查询到要插入或删除的位置的时间。

集合 是否可重复 是否有序 插入复杂度 删除复杂度 获取最大最小复杂度
ArrayList O(n) O(n) O(n)
LinkedList O(n) O(n) O(n)
HashSet O(1) O(1) O(n)
TreeSet O(log n) O(log n) O(log n)
PriorityQueue O(log n) O(n) O(log n)
TreeMultiSet O(log n) O(log n) O(log n)

由上表可以发现本库实现的TreeMultiSet功能最强大,在保证可重复有序的情况下,持频繁插入删除操作的时间复杂度都可以达到O(log n)的级别。当然,应该具体问题具体分析。比如,没有remove中间某个元素且需要可重复元素有序的情况下,PriorityQueue(底层实现是堆)的性能最佳。

最后

觉得还不错的话,就点个star支持一下咯~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • 四、集合框架 1:String类:字符串(重点) (1)多个字符组成的一个序列,叫字符串。生活中很多数据的描述都采...
    佘大将军阅读 749评论 0 2
  • 概述 集合框架被设计成要满足以下几个目标: 该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必...
    Tian_Peng阅读 369评论 0 1
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,171评论 0 3
  • Java集合框架 Java平台提供了一个全新的集合框架。“集合框架”主要由一组用来操作对象的接口组成。不同接口描述...
    小石38阅读 360评论 0 0
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,380评论 0 5