数据结构之集合Set
1、高层的数据结构,集合Set和映射Map,什么是高层的数据结构呢,比如说是栈和队列,这种数据结构更像是先定义好了使用接口,有了这些使用接口,包括数据结构本身所维持的一些性质,可以很方便的放入到一些应用中,但是底层实现可以多种多样的,比如栈和队列,底层实现既可以是动态数据,也可以是链表。
集合就是承载元素的容器,集合Set中有一个重要的特性,就是每个元素在集合中只能存在一次,可以快速帮助去重工作,去重就是去除重复的元素,让所有的元素只保留一份。
2、基于二分搜索树实现的Set集合。代码,如下所示:
首先定义一个接口,然后分别使用二分搜索树的方式和链表的方式实现集合的功能。
1 package com.set; 2 3 /** 4 * @ProjectName: dataConstruct 5 * @Package: com.set 6 * @ClassName: Set 7 * @Author: biehl 8 * @Description: ${description} 9 * @Date: 2020/3/14 10:41 10 * @Version: 1.0 11 */ 12 public interface Set<E> { 13 14 15 /** 16 * Set集合的新增 17 * 18 * @param e 19 */ 20 public void add(E e); 21 22 /** 23 * 删除集合的元素 24 * 25 * @param e 26 */ 27 public void remove(E e); 28 29 /** 30 * 判断是否包含某个元素 31 * 32 * @param e 33 * @return 34 */ 35 public boolean contains(E e); 36 37 /** 38 * 获取集合的个数 39 * 40 * @return 41 */ 42 public int getSize(); 43 44 /** 45 * 判断集合是否为空 46 * 47 * @return 48 */ 49 public boolean isEmpty(); 50 }
使用二分搜索树的方式实现,代码如下所示:
1 package com.set; 2 3 import com.tree.BinarySearchTree; 4 5 /** 6 * 基于二分搜索树实现的Set集合 7 * 8 * @ProjectName: dataConstruct 9 * @Package: com.set 10 * @ClassName: BSTSet 11 * @Author: biehl 12 * @Description: ${description} 13 * @Date: 2020/3/14 10:44 14 * @Version: 1.0 15 */ 16 public class BSTSet<E extends Comparable<E>> implements Set<E> { 17 18 // 定义二分搜索树 19 private BinarySearchTree<E> binarySearchTree; 20 21 /** 22 * 23 */ 24 public BSTSet() { 25 // 无参构造函数,创建二分搜索树对象 26 binarySearchTree = new BinarySearchTree<E>(); 27 } 28 29 @Override 30 public void add(E e) { 31 // 对于重复的元素,不进行任何操作 32 binarySearchTree.add(e); 33 } 34 35 @Override 36 public void remove(E e) { 37 binarySearchTree.remove(e); 38 } 39 40 @Override 41 public boolean contains(E e) { 42 return binarySearchTree.contains(e); 43 } 44 45 @Override 46 public int getSize() { 47 return binarySearchTree.size(); 48 } 49 50 @Override 51 public boolean isEmpty() { 52 return binarySearchTree.isEmpty(); 53 } 54 55 public static void main(String[] args) { 56 BSTSet<String> bstSet = new BSTSet<String>(); 57 // 集合Set的新增操作 58 for (int i = 0; i < 100; i++) { 59 bstSet.add(i + ""); 60 } 61 62 for (int i = 0; i < bstSet.getSize(); i++) { 63 System.out.println(bstSet.toString()); 64 } 65 66 // 集合Set的删除操作 67 bstSet.remove(0 + ""); 68 69 // 集合Set的是否包含某个元素 70 boolean contains = bstSet.contains(0 + ""); 71 System.out.println(contains); 72 73 // 集合Set的大小 74 System.out.println(bstSet.getSize()); 75 76 // 判断集合Set是否为空 77 System.out.println(bstSet.isEmpty()); 78 } 79 80 }
3、二分搜索树和链表都是属于动态数据结构。二分搜索树和链表的数据都是存储到Node节点中的。
1 package com.set; 2 3 import com.linkedlist.LinkedList; 4 5 /** 6 * @ProjectName: dataConstruct 7 * @Package: com.set 8 * @ClassName: LinkedListSet 9 * @Author: biehl 10 * @Description: ${description} 11 * @Date: 2020/3/14 11:54 12 * @Version: 1.0 13 */ 14 public class LinkedListSet<E> implements Set<E> { 15 16 private LinkedList<E> linkedList; 17 18 /** 19 * 无参构造函数,对linkedList进行初始化 20 */ 21 public LinkedListSet() { 22 linkedList = new LinkedList<E>(); 23 } 24 25 @Override 26 public void add(E e) { 27 // 避免将重复的元素添加进去 28 if (!linkedList.contains(e)) { 29 linkedList.addFirst(e); 30 } 31 } 32 33 @Override 34 public void remove(E e) { 35 linkedList.removeElement(e); 36 } 37 38 @Override 39 public boolean contains(E e) { 40 return linkedList.contains(e); 41 } 42 43 @Override 44 public int getSize() { 45 return linkedList.getSize(); 46 } 47 48 @Override 49 public boolean isEmpty() { 50 return linkedList.isEmpty(); 51 } 52 53 public static void main(String[] args) { 54 LinkedListSet<Integer> linkedListSet = new LinkedListSet<Integer>(); 55 // 基于链表实现的集合的新增 56 for (int i = 0; i < 100; i++) { 57 linkedListSet.add(i); 58 } 59 60 // 集合Set的删除操作 61 linkedListSet.remove(0); 62 63 // 集合Set的是否包含某个元素 64 boolean contains = linkedListSet.contains(0); 65 System.out.println(contains); 66 67 // 集合Set的大小 68 System.out.println(linkedListSet.getSize()); 69 70 // 判断集合Set是否为空 71 System.out.println(linkedListSet.isEmpty()); 72 73 } 74 }
4、基于链表的集合实现的性能,慢与基于二分搜索树的集合实现。
集合Set的时间复杂度分析。
1)、增加add。
方式一,基于链表实现的LinkedListSet,本来在链表中添加一个元素时间复杂度是O(1)的,但是对于集合Set来说,需要去除重复元素,所以对于链表需要先查询一遍,查询的时间复杂度O(1),所以整体上,基于LinkedListSet方式实现的的新增操作,时间复杂度是O(1)。
方式二,基于二分搜索树的实现的BSTSet,新增操作,每次新增都可以排除一半元素,因为大于根节点去右子树,小于根节点去左子树,新增操作从根节点向叶子节点出发,一层一层的向下走,经历的节点是二分搜索树的深度,新增操作、查询元素、删除元素都是这个思路,那么平均时间复杂度的是O(h)或者O(logn),其中h是二分搜索树的深度。最差的效果是时间复杂度的是O(n)。解决这个问题可以使用平衡二叉树。
2)、查询contails。
方式一,基于链表实现的LinkedListSet,查询操作的时间复杂度是O(1),因为要把所有的元素遍历。
方式二,基于二分搜索树的实现的BSTSet,那么平均时间复杂度的是O(h)或者O(logn),其中h是二分搜索树的深度。最差的效果是时间复杂度的是O(n)。解决这个问题可以使用平衡二叉树。
3)、删除remove。
方式一,基于链表实现的LinkedListSet,删除操作的时间复杂度是O(1),因为需要先找到待删除元素的前面哪一个节点,再将这个元素删除。
方式二,基于二分搜索树的实现的BSTSet,那么平均时间复杂度的是O(h)或者O(logn),其中h是二分搜索树的深度。最差的效果是时间复杂度的是O(n)。解决这个问题可以使用平衡二叉树。
总结,那么n和h的比较是怎么样的呢,对于一棵满二叉树来说,如果一共有h层的话,节点个数一共是2的h次方减一个节点。那么2^h-1 = n,则h = log2(n + 1),log以2为底的n + 1的对数。h = O(logn),此时不管以那个数字为底的,直接简写成h = O(logn)。