集合Set
- 集合的特点
- 集合的内部实现(使用链表)
- 集合的内部实现(使用红黑树)
- 复杂度分析
- 使用红黑树实现集合的限制
集合的特点
- 不存放重复的元素
- 常用于去重
例如:存放新增的IP地址,统计新增IP量;存放词汇,统计词汇量。。。
集合不允许有重复的元素存在,因子当新添加的元素已经存在集合中,就会去重。集合的特点就是保证,元素的唯一性。
集合的内部实现(使用链表)
集合的内部接口,大概如下:
集合其实也可以用线性表实现,最最不同的特点,就是集合不允许重复元素。所以,我们在设计集合时,只需要注重这一点即可。
可以实现集合的数据结构有很多,比如:动态数组,链表,红黑树。。。
我们先用链表来实现集合。
- 首先,定义一下集合的接口:
public interface Set<Type> {
int size();
boolean isEmpty();
void clear();
boolean contains(Type element);
void add(Type element);
void remove(Type element);
void traversal(Visitor<Type> visitor);
public static abstract class Visitor<Type>{
boolean stop;
public abstract boolean visit(Type element);
}
}
- 然后定义一个ListSet类,实现这些接口:内部使用链表实现。需要注意的就只是添加元素的时候,保证不重复添加即可。
public class ListSet<Type> implements Set<Type> {
//使用链表来实现集合,这里定义一个链表
private List<Type> list = new LinkedList<>();
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void clear() {
list.clear();
}
@Override
public boolean contains(Type element) {
return list.contains(element);
}
@Override
public void add(Type element) {
//添加就不能直接调用list的添加了,需要引入集合特色\
//第一种做法:如果存在,那就不添加当前
if(list.contains(element)) return;
list.append(element);
//第一种做法:如果存在,那就覆盖掉原来的
int index = list.indexOf(element);
if(index != -1){
//说明原本集合里就有element,那就覆盖
list.set(index,element);
}else {
//不存在,那就添加
list.append(element);
}
}
@Override
public void remove(Type element) {
//删除的话,首先判断存不存在,如果集合中确实有元素element再执行删除操作
if(list.indexOf(element) != -1){
list.remove(list.indexOf(element));
}
}
@Override
public void traversal(Visitor<Type> visitor) {
//因为是链表,因此内部可以用for循环实现集合的遍历,取出集合中每一个元素交给visitor去使用
if(visitor == null) return;
int size = list.size();
for (int i = 0; i < size; i++) {
visitor.visit(list.get(i));
}
}
}
OK,我们来测试一下:
public static void main(String[] args) {
Set<Integer> ListSet = new ListSet<>();
ListSet.add(10);
ListSet.add(11);
ListSet.add(11);
ListSet.add(12);
ListSet.traversal(new Set.Visitor<Integer>() {
@Override
public boolean visit(Integer element) {
System.out.println(element);
return false;
}
});
}
可以看到,元素并没有被重复添加,集合的性质也得到了实现。
集合的内部实现(使用红黑树)
用红黑树来实现集合,也就意味着,我们集合中的所有元素,本质上是在红黑树上。
因为红黑树在添加节点的时候,会不断地比较大小,然后判断添加到左子树还是右子树。一旦发现,添加的节点元素等于某个节点元素值,就会覆盖。因此红黑树内部就实现了去重的功能。因此,集合在实现add方法时,直接调用红黑树的add方法就行了,不用做任何额外操作。
public class TreeSet<Type> implements Set<Type> {
private RBTree<Type> tree = new RBTree<>();
@Override
public int size() {
return tree.size();
}
@Override
public boolean isEmpty() {
return tree.isEmpty();
}
@Override
public void clear() {
tree.clear();
}
@Override
public boolean contains(Type element) {
return tree.contains(element);
}
@Override
public void add(Type element) {
//红黑树有一个特点就是,比较,然后添加元素。如果发现相同,就会覆盖点原节点的值
//也就是说,红黑树默认是去重的,因此对于集合的添加,不需要额外操作
tree.add(element);
}
@Override
public void remove(Type element) {
tree.remove(element);
}
@Override
public void traversal(Visitor<Type> visitor) {
//使用tree的中序遍历即可
tree.InorderTraversal();
}
}
复杂度分析
复杂度对比:红黑树实现 vs 链表实现
对于红黑树实现来说,增加删除搜索,集合直接调用红黑树的接口,因此复杂度都是logn级别的。
对于链表实现来说,增加删除搜索,集合也是直接调用链表的接口,因此搜索来说,是O(n)级别的;添加节点的时候,需要搜索然后再添加,也是O(n)级别的;删除节点,也是O(n)级别的;
因此,从复杂度来说,红黑树实现是优于链表的。
我们做一个实际测试:对256018个单词进行add,contains,remove。用链表实现的set耗时7.089秒,用红黑树实现的set耗时0.169秒。这是多么大的差距啊!因此,红黑树真的很强。
使用红黑树实现集合的限制
使用红黑树实现集合的一个最大限制就是:添加进集合的元素,必须具备可比较性!
如果将来我添加进去的元素不具备可比较性,那就不能使用红黑树来实现set了,也就是不能使用TreeSet了。
如果将来,我真的有一堆没有可比较性的数据,还想获得TreeSet一样的性能,怎么办呢?
那就要使用:哈希表!!!后续会介绍