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变量来使用比较器。
我们以("王五",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的话两个方法里指的都是当前被比较的节点。
因此我们:
含义相同,结果都是: