Java中的TreeSet

TreeSet特点:

保证元素唯一
可以给元素进行排序
没有索引,不能用普通for循环,
查找效率高
结构为二叉排序树(也叫二叉查找树)

自然排序Comparable:

自然排序要求类实现了自然排序接口



这里出现异常的原因是我们的Aniki并没有实现Comparable接口,TreeSet并不知道如何排序所以出问题。


我们给Aniki类添加Comparable接口,必须重写compareTo方法,当return 0的时候:

输出只有:
这是因为第一个元素添加的时候是不需要跟别人比较的,第二个元素的时候是需要跟别人比较的,会按照我们给的排序规则比较,
而我们给的规则是compareTo方法给的是0,当返回值是0的时候,会认为第二个元素与第一个元素是同一个元素。所以第二个元素添加失败,
同理第三个也失败。
当compareTo方法return 1(正数)的时候,会按照输入的顺序输出,也就是认为我们输入的顺序是升序。
当compareTo方法return -1(负数)的时候,会按照与输入相反的顺序输出,也就是降序。

compareTo的自定义重写

我们要想要按照Age排列,要这样写


是为什么!?

原因:

分析:

注意:以下文字中会出现二叉树,左儿子,右儿子,前序遍历等词汇,请各位同学在《数据结构》中查找含义。

以上图为例,我们的"99"是第三个要传入的元素,

"99"进入add(),被调入put,然后我们的"99"赋值给K类型的key,

然后我们看到了建立root的过程,root就是二叉树的根,指向树根的指针是t(熟悉的C语言表示形式)。

然后是判断t是否为空,也就是判断根位置有没有元素,如果是空的就把当前的key作为根,

t以后会作为当前位置指针在循环里继续重复这种判断。

可想而知第一个add进来的"100",在这里被作为了根root,(这一步非常关键,

"100"也是之后每次新元素添加的时候,调用下面compareTo(参数)中do——while循环的第一轮的参数,

第一轮循环回让新元素判为处于根节点左子树或者右子树中,第二轮循环会让根节点左儿子或者右儿子

为根,再以新的根持续判断,但是唯一不变的是每个元素第一次进来都会先与整个二叉树的根root作比较)

与我们"99"无关,我们是第三个进来的元素,

然后看到了一个conparator构造器的判断语句,毫无疑问我们并没有new一个构造器,我们使用了无参的构造方法,

因此我们的构造器cpr是null,不进入这个if语句。

来到了else部分,判断了我们存储"99"的key是否是null,是的话抛出空指针异常,我们的key不空。

之后是一个类型转换,我们的key元素是K类型,K类型继承不继承Comparable不知道,但是我们add(99)中使用的类型

毫无疑问是implements Comparable的,否则我们会出现之前的异常,原因是:Aniki并没有实现Comparable接口

因此我们可以强制类型转换为Comparable的k,这里是多态的向上转型。

然后我们就进入了构建二叉树的do-while循环(循环内的含义参考《数据结构》)这颗二叉树的元素从左往右依次增大。

我们先与根节点比较,再次提一下这个根节点是我们第一个add的"100",当前的t是指向root根的指针,t.key就是100,

这里二叉树构建的依据是,compareTo(100)方法的返回值int类型的cmp,而我们就可以在这里做手脚。

k是我们转换好的key,"99",是我们的"99"调用的compareTo(100),因此this指的是新传入值为"99"的变量。

在我们的哲♂学例子,更改compareTo(参数)方法,来概变二叉树的真实构建情况,
Age从小到大如图:

我们比较Aniki年龄来排序的时候,

“k”是我们的第二个元素Bili的Age——"50",而根root,也就是“t.key”,是我们第一个元素Van的Age——"51"。

“k”调用compareTo(51),那么“k”就是this,this.Age就是Bili的Age——"50"

aniki.Age就是Van的Age——"51"

return this.Age - aniki.Age;这句话的意思就成了

return Bili.Age - Van.Age;结果是-1,负数,Bili进入根Van的左儿子,前序遍历的结果是年龄从小到大。

因此如果我们想从大到小遍历,那么我们写:

return aniki.Age - this.Age;结果是:+1,Bili被放到Van的右儿子,前序遍历的结果是从大到小。

Age从大到小:

Comparator比较器排序:

我们在新建TreeSet的时候,可以不执着于使用自然排序(实现Comparable接口然后重写compareTo方法),我们可以使用
Comparator比较器来完成排序(此时就不会使用自然排序)。

Comparator比较器使用方法:


我们新建一个TreeSet并且这样new一个Comparator比较器,然后使用匿名内部类重写compare方法,


简单的修改参数含义,第一个参数是新元素,第二个元素就是我们之前提过的(t指针)元素,它在do——while第一轮中是根节点。
一开始默认return 0,与compareTo(){return 0;}的时候是一样的。

我们来讲解下原理:


根据上图的例子来讲,
我们new出一个Comparator之后,这个Comparator会被传入final最终态的comparator变量中,之后我们通过
这个comparator变量来使用比较器。

我们以("王五",15)这个元素来看整个过程,("王五",15)被add方法调用的put方法中,值传递给了K类型的key。

然后与之前一样,第一个add进TreeSet来的元素("张三",13)被当作根节点。只不过这次我们判断有没有comparator时,

我们具备了comparator。于是进入do——while循环,这里构造器调用compare(key,t.key),

第一个位置是当前传入的元素,第二个位置是指针指向的节点,与之前的compareTo(参数)方法大同小异。

只不过compareTo是k(值为key的变量)调用的,k.compareTo(t.key)方法中,

this.key就是compare(key,t.key)中的key,t.key的话两个方法里指的都是当前被比较的节点。

因此我们:


含义相同,结果都是:

posted @ 2019-11-18 09:02  卯毛  阅读(797)  评论(0编辑  收藏  举报