集合(Set)和映射(Map)基础总结

集合(Set)和映射(Map)基础总结

  • Set介绍
  • 基于链表实现Set
  • 基于二叉搜索树实现Set
  • 基于二叉平衡树实现Set
  • 使用Set解决LeetCode-804. Unique Morse Code Words
  • 使用Set解决LeetCode-349. Intersection of Two Arrays
  • Map介绍
  • 基于链表实现Map
  • 基于二叉搜索树实现Map
  • 基于二叉平衡树实现Map
  • 使用Map解决LeetCode-350. Intersection of Two Arrays II
  • SetMap的一些对比和总结

Set介绍

  • Set是不允许重复的集合容器;
  • Set可以分为有序集合和无需集合;
  • 有序集合基于搜索树实现,JDK底层是红黑树,即TreeSet
  • 无序集合基于Hash表实现,JDK底层是HashMap包装之后,即HashSet

基于链表实现Set

首先看一下单链表的实现,下面的实现中使用SingleList实现:
先看Set接口中需要实现的方法:

/**
 * Set接口
 * @param <E>
 */
public interface Set<E> {
    void add(E e);
    boolean contains(E e);
    void remove(E e);
    int getSize();
    boolean isEmpty();
}

如果有单链表的相关方法,其中的Set就很容易实现,唯一要注意的就是 在添加元素的时候要注意先判断集合中有没有这个元素:

/**
 * 基于单链表实现 Set
 * @param <E>
 */
public class LinkedListSet<E> implements Set<E> {

    private SingleList<E> list;

    public LinkedListSet(){
        list = new SingleList<>();
    }

    @Override
    public int getSize(){
        return list.size();
    }

    @Override
    public boolean isEmpty(){
        return list.isEmpty();
    }

    @Override
    public void add(E e){
        if(!list.contains(e)) //这个就是效率低的原因
            list.addFirst(e);
    }

    @Override
    public boolean contains(E e){
        return list.contains(e);
    }

    @Override
    public void remove(E e){
        list.removeElement(e);
    }

}

基于二叉搜索树实现Set

先看一下二叉搜索树相关方法的实现。注意由于我实现的二叉搜索树没有重复元素,所有Set也没有重复元素。

/**
 * 基于二叉搜索树实现的Set  类似JDK的TreeSet (有序集合)   而HashSet是无序集合
 * @param <E>
 */
public class BSTSet<E extends Comparable<E>> implements Set<E> {

    private BSTree<E> bst;

    public BSTSet(){
        bst = new BSTree<>();
    }

    @Override
    public int getSize(){
        return bst.size();
    }

    @Override
    public boolean isEmpty(){
        return bst.isEmpty();
    }

    @Override
    public void add(E e){
        bst.add(e);
    }

    @Override
    public boolean contains(E e){
        return bst.contains(e);
    }

    @Override
    public void remove(E e){
        bst.remove(e);
    }
}

基于二叉平衡树实现Set

在这之前先看一下二叉平衡树的总结和代码实现。

public class AVLSet<E extends Comparable<E>> implements Set<E> {

    private AVLTree<E, Object> avl;

    public AVLSet(){
        avl = new AVLTree<>();
    }

    @Override
    public int getSize(){
        return avl.size();
    }

    @Override
    public boolean isEmpty(){
        return avl.isEmpty();
    }

    @Override
    public void add(E e){
        avl.add(e, null);
    }

    @Override
    public boolean contains(E e){
        return avl.contains(e);
    }

    @Override
    public void remove(E e){
        avl.remove(e);
    }
}

使用Set解决LeetCode-804. Unique Morse Code Words

题目链接

在这里插入图片描述
很简单的题目,转换成对应的解码,然后存到一个不能重复的set集合中,返回集合中的元素个数即可;

   public int uniqueMorseRepresentations(String[] words) {
        if (words == null || words.length == 0) {
            return 0;
        }
        String[] dict = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."};
        HashSet<String>set = new HashSet<>();
        for(String word : words){
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < word.length(); i++){
                sb.append(dict[word.charAt(i) - 'a']);
            } 
            set.add(sb.toString());
        }
        return set.size();
    }

使用Set解决LeetCode-349. Intersection of Two Arrays

题目链接

在这里插入图片描述
解题思路: 我们可以使用一个set一开始去除nums1中的重复元素,遍历nums2中的元素,判断每个元素在nums1中是否出现,为了不重复,记录一个之后要在set中删除这个

   public int[] intersection(int[] nums1, int[] nums2) {
        HashSet<Integer>set = new HashSet<>();
        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);// important
            }
        }
        int[] res = new int[list.size()];
        for(int i = 0 ; i < list.size() ; i ++)
            res[i] = list.get(i);
        return res;
    }

另外这个题目基于双指针和二分Ologn的实现也不难,可以看下这里


Map介绍

  • Map的键不允许重复,如果重复插入键相同的,则新的value覆盖原来的value
  • Map可以分为有序Map和无序Map
  • 有序Map基于搜索树实现,JDK底层使用红黑树实现,即TreeMap
  • 无序Map基于Hash表实现,JDK底层使用Hash表底层实现,即HashMap

基于链表实现Map

和普通的单链表不同的:

  • 结点内部是key,value的键值对;
  • getNode(),返回key对应的结点,方便操作;
  • add()操作的时候,如果已经存在key对应的结点,就更新value即可;
public class LinkedListMap<K, V> implements Map<K, V> {

    //结点结构
    private class Node {
        public K key;
        public V value;
        public Node next;

        public Node(K key, V value, Node next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public Node(K key, V value) {
            this(key, value, null);
        }

        public Node() {
            this(null, null, null);
        }

        @Override
        public String toString() {
            return key.toString() + " : " + value.toString();
        }
    }

    private Node dummyHead;
    private int size;

    public LinkedListMap() {
        dummyHead = new Node();
        size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    
    // important
    private Node getNode(K key) {
        Node cur = dummyHead.next;
        while (cur != null) {
            if (cur.key.equals(key))
                return cur;
            cur = cur.next;
        }
        return null;
    }

    @Override
    public boolean contains(K key) {
        return getNode(key) != null;
    }

    @Override
    public V get(K key) {
        Node node = getNode(key);
        return node == null ? null : node.value;
    }

    @Override
    public void add(K key, V value) {
        Node node = getNode(key);
        if (node == null) {
            /**Node newNode = new Node(key,value);
            newNode.next = dummyHead.next;
            dummyHead.next = newNode;*/
            dummyHead.next = new Node(key, value, dummyHead.next);// == 上面三行
            size++;
        } else //already exist
            node.value = value;
    }

    @Override
    public void set(K key, V newValue) {
        Node node = getNode(key);
        if (node == null)
            throw new IllegalArgumentException(key + " doesn't exist!");

        node.value = newValue;
    }

    @Override
    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;
    }
}

基于二叉搜索树实现Map

二叉搜索树中的操作差不多,注意:

  • 在添加的时候,如果已经存在,也就是key相等的话就直接更新value即可;
  • 增加getNode()方法: 返回以node为根节点的二分搜索树中,key所在的节点;
  • 大部分对于e(也就是结点值)的操作,就是对key的操作;
public class BSTMap<K extends Comparable<K>, V> implements Map<K, V> {

    private class Node {
        public K key;
        public V value;
        public Node left, right;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            left = null;
            right = null;
        }
    }

    private Node root;
    private int size;

    public BSTMap() {
        root = null;
        size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    // 向二分搜索树中添加新的元素(key, value)
    @Override
    public void add(K key, V value) {//相当于JDK的put操作
        root = add(root, key, value);
    }

    // 向以node为根的二分搜索树中插入元素(key, value),递归算法
    // 返回插入新节点后二分搜索树的根
    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   // key.compareTo(node.key) == 0
            node.value = value;

        return node;
    }

    // 返回以node为根节点的二分搜索树中,key所在的节点
    private Node getNode(Node node, K key) {

        if (node == null)
            return null;

        if (key.equals(node.key))
            return node;
        else if (key.compareTo(node.key) < 0)
            return getNode(node.left, key);
        else // if(key.compareTo(node.key) > 0)
            return getNode(node.right, key);
    }

    @Override
    public boolean contains(K key) {
        return getNode(root, key) != null;
    }

    @Override
    public V get(K key) {
        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }

    @Override
    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;
    }

    // 返回以node为根的二分搜索树的最小值所在的节点
    private Node minimum(Node node) {
        if (node.left == null)
            return node;
        return minimum(node.left);
    }

    // 删除掉以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;
    }

    // 从二分搜索树中删除键为key的节点
    @Override
    public V remove(K key) {

        Node node = getNode(root, key);
        if (node != null) {
            root = remove(root, key);
            return node.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;
        }
    }
}

基于二叉平衡树实现Map

在这之前先看平衡二叉树的原理和实现。

public class AVLMap<K extends Comparable<K>, V> implements Map<K, V> {

    private AVLTree<K, V> avl;

    public AVLMap(){
        avl = new AVLTree<>();
    }

    @Override
    public int getSize(){
        return avl.size();
    }

    @Override
    public boolean isEmpty(){
        return avl.isEmpty();
    }

    @Override
    public void add(K key, V value){
        avl.add(key, value);
    }

    @Override
    public boolean contains(K key){
        return avl.contains(key);
    }

    @Override
    public V get(K key){
        return avl.get(key);
    }

    @Override
    public void set(K key, V newValue){
        avl.set(key, newValue);
    }

    @Override
    public V remove(K key){
        return avl.remove(key);
    }
}

使用Map解决LeetCode-350. Intersection of Two Arrays II

题目链接

在这里插入图片描述
解题思路: 也很简单,只需要用map记录一下次数。然后在map中更新维护次数即可。

 public int[] intersect(int[] nums1, int[] nums2) {
        HashMap<Integer,Integer>map = new HashMap<>();
        
        
        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 < list.size(); i++) res[i] = list.get(i);
        return res;
    }

Set和Map的一些对比和总结

  • 集合Set和映射Map有很多的相似之处,实现都可以使用链表和搜索树实现;
  • 其中JDK的HashSet也是HashMap包装之后的,虽然用的是Hash表。
  • 使用搜索树实现的SetMap平均时间复杂度为Ologn(Ologh)(Olog2n),但是使用链表是O(n)
Set< E >Map<K,V>
void add(E)void add(K, V)
void remove(E)V remove(K)
boolean contains(E)boolean contains(K)
int getSize()int getSize()
boolean isEmpty()boolean isEmpty()
V get(K)
void set(K, V)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值