集合和映射
集合
1、集合
元素只存在一次,进行去重操作。
二分搜索树不能盛放重复元素,所以是非常好的实现“集合”的底层数据结构。
- void add(E),不能添加重复元素
- void remove(E)
- boolean contains(E)
- int getSize()
- boolean isEmpty()
典型应用:客户统计、词汇量统计
基于二分搜索树的集合的实现:
public class BSTSet<E extends Comparable<E>> implements Set<E> {
private BST<E> bst;
public BSTSet() {
bst=new BST<>();
}
//方法重写调用bst的方法
}
基于链表的集合的实现:
public class LinkedListSet<E> implements Set<E> {
private LinkedList<E> list;
public LinkedListSet() {
list=new LinkedList<>();
}
public int getSize() {
return list.getSize();
}
public boolean isEmpty() {
return list.isnull();
}
public void add(E e) {
if(!list.contains(e)) {//链表contains时间复杂的为O(n)
list.addFirst(e);//在链表头添加节点时间复杂度为O(1)
}
}
public boolean contains(E e) {
return list.contains(e);
}
public void remove(E e) {
list.removeElement(e);
}
}
2、复杂度分析
二分搜索树比链表更高效。
测试
对于集合:
链表 | 二分搜索树 | 二分搜索树最坏 | |
---|---|---|---|
增 | O(n) | O(h)=O(logn)h是树的高度 | O(n) |
删 | O(n) | O(h)=O(logn) | O(n) |
查 | O(n) | O(h)=O(logn) | O(n) |
从0层开始到h层,共有多少个节点?
2
0
+
2
1
+
…
…
+
2
h
−
1
=
2
h
−
1
=
n
2^0+2^1+ …… +2^ {h-1}=2^h-1=n
20+21+……+2h−1=2h−1=n
h = l o g 2 ( n + 1 ) h=log_2(n+1) h=log2(n+1)
同样的数据可以对应不同的二分搜索树,二分搜索树可能退化成链表(最坏的情况):
按1,2,3,4,5,6的顺序构建二分搜索树,会导致最差的情况出现

3、LeeCode804–唯一的摩斯码
使用java.util.TreeSet,它是基于红黑树实现的集合。
代码:
import java.util.TreeSet;
class Solution804 {
public int uniqueMorseRepresentations(String[] words) {
String[] codes= {".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."};
TreeSet<String> set=new TreeSet<>();
for(String word:words) {
StringBuilder res=new StringBuilder();
for(int i=0;i<word.length();i++) {
res.append(codes[word.charAt(i)-'a']);
}
set.add(res.toString());
}
return set.size();
}
}
4、其他
有序集合和无序集合
有序集合中的元素具有顺序性:基于搜索树的实现
无序集合中的元素没有顺序性:基于哈希表的实现
多重集合multiset
集合中的元素可以重复
映射
1、映射基础Map
字典(单词–>释意);名册(身份证号–>人);数据库(id–>信息);词频统计(单词–>词频)……
映射:
-
存储(键,值)数据对的数据结构
-
根据键(key),寻找值(value)
-
容易使用链表或二分搜索树实现
如class Node{K key; V value; Node left; Node right;}
映射接口:
- void add(K,V)
- V remove(K)
- boolean contain(K)
- V get(K)
- void set(K,V)
- int getSize()
- boolean isEmpty()
2、基于链表实现映射
1)首先定义Map接口,定义上述函数
public interface Map<K,V> { }
2)LinkedListMap类。
首先定义链表中的结点class Node(key,value,next)以及内部类Node的构造函数;dummyHead和size;
3)重写接口函数
contains和get借助getNode函数
public int getSize() {
return size;
}
public boolean isEmpty() {
return size==0;
}
public Node getNode(K key) {
Node cur=dummyHead.next;
while(cur!=null) {
if(cur.key.equals(key))
return cur;
cur=cur.next;
}
return null;
}
public boolean contains(K key) {
return getNode(key)!=null;
}
public V get(K key) {
Node node=getNode(key);
return node==null?null:node.value;
}
public void add(K key,V value) {
Node node=getNode(key);
if(node==null) {
dummyHead.next=new Node(key,value,dummyHead.next);
size++;
}
else {
node.value=value;//更新节点
}
}
public void set(K key,V newvalue) {
Node node=getNode(key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value=newvalue;
}
public V remove(K key) {
Node prev=dummyHead;
while(prev.next!=null) {
if(prev.next.key.equals(key))
break;
prev=prev.next;//找到删除节点的前一个结点
}
if(prev.next!=null) {
Node delNode=prev.next;
prev.next=delNode.next;
delNode.next=null;
size--;
return delNode.value;
}
return null;
}
4)应用:词频统计
public static void main(String[] args) {
System.out.println("Pride and Prejudice");
ArrayList<String> words = new ArrayList<>();
if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
System.out.println("Total words:"+words.size());
LinkedListMap<String,Integer> map=new LinkedListMap<>();
for(String word:words) {
if(map.contains(word))
map.set(word, map.get(word)+1);
else
map.add(word, 1);
}
System.out.println("Total different words: " + map.getSize());
System.out.println("Frequency of PRIDE: " + map.get("pride"));
System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));
}
System.out.println();
}
3、基于二分搜索树实现映射
1)首先定义Map接口,定义上述函数
public interface Map<K,V> { }
2)定义BSTMap类。
public class BSTMap<K extends Comparable,V> implements Map<K,V> { }
定义内部类Node,和成员变量Node root,int size
3)实现接口方法
public void add(K key,V value) {
root=add(root,key,value);
}
//返回add后BST的根
private Node add(Node node,K key,V value) {
if(node==null) {
size++;
return new Node(key,value);
}
if(key.compareTo(node.key)<0) {
node.left=add(node.left,key,value);
}
else if(key.compareTo(node.key)>0) {
node.right=add(node.right,key,value);
}else {//修改为新的值
node.value=value;
}
return node;
}
// 返回以node为根节点的二分搜索树中,key所在的节点
private Node getNode(Node node,K key) {
if(node==null)
return null;
if(key.equals(key))
return node;
else if(key.compareTo(node.key)<0)
return getNode(node.left,key);
else
return getNode(node.right,key);
}
public boolean contains(K key) {
return getNode(root,key)!=null;
}
public V get(K key) {
Node node=getNode(root,key);
return node==null?null:node.value;
}
public void set(K key,V newvalue) {
Node node=getNode(root,key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value=newvalue;
}
private Node minimum(Node node) {
if(node.left==null)
return node;
node=node.left;
return node;
}
private Node removeMin(Node node) {
if(node.left==null) {
Node rightNode=node.right;
node.right=null;
size--;
return rightNode;
}
node.left=removeMin(node.left);
return node;
}
public V remove(K key) {
Node node=getNode(root,key);
if(node!=null) {
root=remove(root,key);
return root.value;
}
return null;
}
private Node remove(Node node, K key){
if( node == null )
return null;
if( key.compareTo(node.key) < 0 ){
node.left = remove(node.left , key);
return node;
}
else if(key.compareTo(node.key) > 0 ){
node.right = remove(node.right, key);
return node;
}
else{ // key.compareTo(node.key) == 0
// 待删除节点左子树为空的情况
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 待删除节点右子树为空的情况
if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
// 待删除节点左右子树均不为空的情况
// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
// 用这个节点顶替待删除节点的位置
Node successor = minimum(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
4、时间复杂度
BSTMap大大强于LinkedListMap

针对最差的情况,引入平衡二叉树。
集合和映射的关系
若已有映射的底层实现,可直接实现集合:将value视为空。
LeetCode
349 and 350
两个数组的交集:有重复元素和无重复元素。
分别使用集合和映射(key:num,value:频次)实现。
349:
import java.util.ArrayList;
import java.util.TreeSet;
public class Solution349 {
public int[] intersection(int[] nums1, int[] nums2) {
TreeSet<Integer> set=new TreeSet<>();
for(int num:nums1)
set.add(num);
ArrayList<Integer> list=new ArrayList<>();
for(int num:nums2) {
if(set.contains(num)) {
list.add(num);
set.remove(num);
}
}
int[] res=new int[list.size()];
for(int i=0;i<list.size();i++)
res[i]=list.get(i);
return res;
}
}
350:
import java.util.TreeMap;
public class Solution350 {
public int[] intersect(int[] nums1, int[] nums2) {
TreeMap<Integer,Integer> map=new TreeMap<>();
for(int num:nums1) {
if(!map.containsKey(num)) {
map.put(num, 1);
}else {
map.put(num, map.get(num)+1);
}
}
ArrayList<Integer> list=new ArrayList<>();
for(int num:nums2) {
if(map.containsKey(num)) {
list.add(num);
map.put(num, map.get(num)-1);
if(map.get(num)==0)
map.remove(num);
}
}
int[] res=new int[list.size()];
for(int i=0;i<res.length;i++)
res[i]=list.get(i);
return res;
}
}