背景是
需要一种数据结构:
- 支持添加元素进入该结构、或根据元素内容从结构中移除该元素;
- 该结构支持按给定的排序规则遍历。
于是就想到了 priorityQueue!!!
给它定义一个按字符串的长度倒序排序:
priorityQueue = new PriorityQueue<>(Comparator.comparingInt(String::length).reversed());
一顿添加之后:
发现:顺序竟然不对?! 丝毫没有向想象中的结果发展。。。即使用内置的迭代器遍历结果也是一样的: ) 难道java有bug??
于是赶紧面向百度编程,并没有反转:
priorityQueue内部是一个小根堆结构!!然而而堆是一种不完全有序的结构,而这个堆的根节点就是队列的队头。
如果要获得有序的特性, 逐个出队(poll) :行! 用迭代器:不行!
而实际上每次做出队操作,都是在删除根节点,假定现在我们需要删除头部元素3,我们主要还是两个步骤:
- 用尾元素替换头元素
- 用头元素和两个孩子中值较小的相比较,如果小于该节点的值则不做任何调整,否则交换,再作为子树根节点做同样的逻辑。
这个过程是 O(logn) 的时间复杂度,实在不能接受!
而该结构的 插入元素(offer)的过程,也伴随着调整结构
- 将新元素添加到堆结构的末尾
- 比较该元素和父节点的大小,如果小于父节点就进行调整,直至满足堆结构,最差会直接到根节点比较
显然插入过程的时间复杂度也取决于树的高度,同样是 O(logn)
Q:如果我要把n个数放进去priorityQueue,并且按序遍历m次,遍历和插入的先后顺序不确定,总的时间复杂度是多少?
A: n * logn + m * n * logn
麻了麻了 简直太慢了: )
于是又想到了TreeSet
赶紧new了个压压惊
keywordsTreeSet = new TreeSet<>(Comparator.comparingInt(String::length).reversed());
我直接add了几个,很快啊
然后remove了一个其中不存在的元素
filterSystem.remove("he");
它 变了!!
我直接好家伙,remove的是"he" "hi"怎么没了?难道jdk有bug??
这次面向百度编程也没找到大佬解读,只能自己看看代码,看到TreeMap.java中有这样一段逻辑:
然后它的返回值变成了"hi" !
竟然remove的时候用了实例化TreeSet时的Comparator来区分元素???意思我定义的是按长度比较,add一个两个字符长度的进去,随便remove一个两个字符长度的就能删。
然而java注释也没说明清楚有这么回事
看来jdk官方的代码注释也不太靠谱的样子。