数据结构与算法-集合与映射

集合(set)

特点

每个元素只能存在一个,可基于二分搜索树或者链表实现

基于二分搜索树的实现

二分搜索树代码

package com.study.set;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

public class BST<E extends Comparable<E>> {
    
    

    private class Node{
    
    
        public E e;
        public Node left, right;

        public Node(E e){
    
    
            this.e = e;
            left = null;
            right = null;
        }
    }

    private Node root;
    private int size;

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

    public int size(){
    
    
        return size;
    }

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

    // 向二分搜索树中添加新的元素e
    public void add(E e){
    
    
        root = add(root, e);
    }

    // 向以node为根的二分搜索树中插入元素e,递归算法
    // 返回插入新节点后二分搜索树的根
    private Node add(Node node, E e){
    
    

        if(node == null){
    
    
            size ++;
            return new Node(e);
        }

        if(e.compareTo(node.e) < 0)
            node.left = add(node.left, e);
        else if(e.compareTo(node.e) > 0)
            node.right = add(node.right, e);

        return node;
    }

    // 看二分搜索树中是否包含元素e
    public boolean contains(E e){
    
    
        return contains(root, e);
    }

    // 看以node为根的二分搜索树中是否包含元素e, 递归算法
    private boolean contains(Node node, E e){
    
    

        if(node == null)
            return false;

        if(e.compareTo(node.e) == 0)
            return true;
        else if(e.compareTo(node.e) < 0)
            return contains(node.left, e);
        else // e.compareTo(node.e) > 0
            return contains(node.right, e);
    }

    // 二分搜索树的前序遍历
    public void preOrder(){
    
    
        preOrder(root);
    }

    // 前序遍历以node为根的二分搜索树, 递归算法
    private void preOrder(Node node){
    
    

        if(node == null)
            return;

        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

    // 二分搜索树的非递归前序遍历
    public void preOrderNR(){
    
    

        Stack<Node> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
    
    
            Node cur = stack.pop();
            System.out.println(cur.e);

            if(cur.right != null)
                stack.push(cur.right);
            if(cur.left != null)
                stack.push(cur.left);
        }
    }

    // 二分搜索树的中序遍历
    public void inOrder(){
    
    
        inOrder(root);
    }

    // 中序遍历以node为根的二分搜索树, 递归算法
    private void inOrder(Node node){
    
    

        if(node == null)
            return;

        inOrder(node.left);
        System.out.println(node.e);
        inOrder(node.right);
    }

    // 二分搜索树的后序遍历
    public void postOrder(){
    
    
        postOrder(root);
    }

    // 后序遍历以node为根的二分搜索树, 递归算法
    private void postOrder(Node node){
    
    

        if(node == null)
            return;

        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e);
    }

    // 二分搜索树的层序遍历
    public void levelOrder(){
    
    

        Queue<Node> q = new LinkedList<>();
        q.add(root);
        while(!q.isEmpty()){
    
    
            Node cur = q.remove();
            System.out.println(cur.e);

            if(cur.left != null)
                q.add(cur.left);
            if(cur.right != null)
                q.add(cur.right);
        }
    }

    // 寻找二分搜索树的最小元素
    public E minimum(){
    
    
        if(size == 0)
            throw new IllegalArgumentException("BST is empty!");

        return minimum(root).e;
    }

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

    // 寻找二分搜索树的最大元素
    public E maximum(){
    
    
        if(size == 0)
            throw new IllegalArgumentException("BST is empty");

        return maximum(root).e;
    }

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

        return maximum(node.right);
    }

    // 从二分搜索树中删除最小值所在节点, 返回最小值
    public E removeMin(){
    
    
        E ret = minimum();
        root = removeMin(root);
        return ret;
    }

    // 删除掉以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 E removeMax(){
    
    
        E ret = maximum();
        root = removeMax(root);
        return ret;
    }

    // 删除掉以node为根的二分搜索树中的最大节点
    // 返回删除节点后新的二分搜索树的根
    private Node removeMax(Node node){
    
    

        if(node.right == null){
    
    
            Node leftNode = node.left;
            node.left = null;
            size --;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    // 从二分搜索树中删除元素为e的节点
    public void remove(E e){
    
    
        root = remove(root, e);
    }

    // 删除掉以node为根的二分搜索树中值为e的节点, 递归算法
    // 返回删除节点后新的二分搜索树的根
    private Node remove(Node node, E e){
    
    

        if( node == null )
            return null;

        if( e.compareTo(node.e) < 0 ){
    
    
            node.left = remove(node.left , e);
            return node;
        }
        else if(e.compareTo(node.e) > 0 ){
    
    
            node.right = remove(node.right, e);
            return node;
        }
        else{
    
       // e.compareTo(node.e) == 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;
        }
    }

    @Override
    public String toString(){
    
    
        StringBuilder res = new StringBuilder();
        generateBSTString(root, 0, res);
        return res.toString();
    }

    // 生成以node为根节点,深度为depth的描述二叉树的字符串
    private void generateBSTString(Node node, int depth, StringBuilder res){
    
    

        if(node == null){
    
    
            res.append(generateDepthString(depth) + "null\n");
            return;
        }

        res.append(generateDepthString(depth) + node.e +"\n");
        generateBSTString(node.left, depth + 1, res);
        generateBSTString(node.right, depth + 1, res);
    }

    private String generateDepthString(int depth){
    
    
        StringBuilder res = new StringBuilder();
        for(int i = 0 ; i < depth ; i ++)
            res.append("--");
        return res.toString();
    }
}

接口代码

package com.study.set;

public interface Set<E> {
    
    

    void add(E e);
    boolean contains(E e);
    void remove(E e);
    int getSize();
    boolean isEmpty();
}

treeSet代码

package com.study.set;

/**
 * Created by Zsy on 2020/8/14.
 */
public class BSTSet<E extends Comparable<E>> implements Set<E> {
    
    
    private BST<E> bst;


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

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

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

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

基于链表实现集合

链表

package com.study.set;

public class LinkedList<E> {
    
    

    private class Node{
    
    
        public E e;
        public Node next;

        public Node(E e, Node next){
    
    
            this.e = e;
            this.next = next;
        }

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

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

        @Override
        public String toString(){
    
    
            return e.toString();
        }
    }

    private Node dummyHead;
    private int size;

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

    // 获取链表中的元素个数
    public int getSize(){
    
    
        return size;
    }

    // 返回链表是否为空
    public boolean isEmpty(){
    
    
        return size == 0;
    }

    // 在链表的index(0-based)位置添加新的元素e
    // 在链表中不是一个常用的操作,练习用:)
    public void add(int index, E e){
    
    

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index.");

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        prev.next = new Node(e, prev.next);
        size ++;
    }

    // 在链表头添加新的元素e
    public void addFirst(E e){
    
    
        add(0, e);
    }

    // 在链表末尾添加新的元素e
    public void addLast(E e){
    
    
        add(size, e);
    }

    // 获得链表的第index(0-based)个位置的元素
    // 在链表中不是一个常用的操作,练习用:)
    public E get(int index){
    
    

        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Illegal index.");

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++)
            cur = cur.next;
        return cur.e;
    }

    // 获得链表的第一个元素
    public E getFirst(){
    
    
        return get(0);
    }

    // 获得链表的最后一个元素
    public E getLast(){
    
    
        return get(size - 1);
    }

    // 修改链表的第index(0-based)个位置的元素为e
    // 在链表中不是一个常用的操作,练习用:)
    public void set(int index, E e){
    
    
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Illegal index.");

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++)
            cur = cur.next;
        cur.e = e;
    }

    // 查找链表中是否有元素e
    public boolean contains(E e){
    
    
        Node cur = dummyHead.next;
        while(cur != null){
    
    
            if(cur.e.equals(e))
                return true;
            cur = cur.next;
        }
        return false;
    }

    // 从链表中删除index(0-based)位置的元素, 返回删除的元素
    // 在链表中不是一个常用的操作,练习用:)
    public E remove(int index){
    
    
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        Node retNode = prev.next;
        prev.next = retNode.next;
        retNode.next = null;
        size --;

        return retNode.e;
    }

    // 从链表中删除第一个元素, 返回删除的元素
    public E removeFirst(){
    
    
        return remove(0);
    }

    // 从链表中删除最后一个元素, 返回删除的元素
    public E removeLast(){
    
    
        return remove(size - 1);
    }

    // 从链表中删除元素e
    public void removeElement(E e){
    
    

        Node prev = dummyHead;
        while(prev.next != null){
    
    
            if(prev.next.e.equals(e))
                break;
            prev = prev.next;
        }

        if(prev.next != null){
    
    
            Node delNode = prev.next;
            prev.next = delNode.next;
            delNode.next = null;
            size --;
        }
    }

    @Override
    public String toString(){
    
    
        StringBuilder res = new StringBuilder();

        Node cur = dummyHead.next;
        while(cur != null){
    
    
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL");

        return res.toString();
    }
}

linklistSet

package com.study.set;

/**
 * Created by Zsy on 2020/8/14.
 */
public class LinkedListSet<E> implements Set<E> {
    
    

    private LinkedList linkedList;

    public LinkedListSet() {
    
    
        linkedList = new LinkedList<>();
    }

    @Override
    public void add(E e) {
    
    
        if (!linkedList.contains(e))
            linkedList.addFirst(e);
    }

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

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

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

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

两种方式实现集合发的复杂度分析

注意:二分搜索树实现的set有可能会退化成链表,此时的时间复杂度为O(n)

					linklistSet         bstSet
	add			O(n)                 O(logn)
	remove  	O(n)                 O(logn)
	contain   	O(n)				 O(logn)

补充:若要实现有序集合,建议用哈希表再封装为set

leetcode中关于集合的题目

804. 唯一摩尔斯密码词

题目链接

解决思路

1. 声明单词对应摩斯码
2. 遍历单词,并将每个单词对应字母寻找对应摩斯码进行拼接
3. 用set集合进行添加,若为重复set就不会添加,问题得以解决

实现代码

public class uniqueMorseRepresentations_804 {
    
    

    public int uniqueMorseRepresentations(String[] words) {
    
    
        String[] codes = {
    
    ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."};
        TreeSet<String> treeSet=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']);
            }
                treeSet.add(res.toString()) ;


        }
        return treeSet.size();
    }
}

映射

概述

数学中的函数就是映射,一一对应,俗称字典

实现

基于二分搜索树

基于链表

package com.study.map;

/**
 * Created by Zsy on 2020/8/14.
 */
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;
    }

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

    }


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

    @Override
    public V remove(K key) {
    
    
        Node prev = dummyHead;
        while (prev.next != null) {
    
    
            if (key.equals(prev.next.key)) {
    
    
                break;
            }
            prev = prev.next;
        }

        if (prev.next != null) {
    
    
            Node delNode = prev.next;
            prev.next = delNode.next;
            size--;
            return delNode.value;
        }
        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 set(K key, V newValue) {
    
    
        Node node = getNode(key);
        if (node == null)
            throw new IllegalArgumentException(key + " doesn't exist!");

        node.value = newValue;
    }

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

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


}

基于二分搜索树

package com.study.map;

/**
 * Created by Zsy on 2020/8/14.
 */
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 void add(K key, V 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 {
    
    
            node.value = value;
        }

        return node;
    }


    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
            return getNode(node.right, key);
    }

    @Override
    public V remove(K key) {
    
    
        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 {
    
    
            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;

        }

    }


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


    private Node minimum(Node node) {
    
    
        if (node.left == null)
            return node;
        return minimum(node.left);

    }

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

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

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

复杂度分析

注意:二分搜索树实现的set有可能会退化成链表,此时的时间复杂度为O(n)

					linklistSet         bstSet
	add			O(n)                 O(logn)
	remove  	O(n)                 O(logn)
	contain   	O(n)				 O(logn)

映射和集合的关系

集合包含于映射,可以用映射实现集合

leetcode中映射和集合的使用场景分门别类讲解

349 两个数组交集1

链接

两个数组交集1

解题思路

这题要求求出两个数组交集,且交集中元素不重复,所以解题步骤为:

1. 使用set遍历存储数组1元素
2. 遍历数组2,若遍历的元素在数组1中也存在,就添加至arraylist中,同时注意数组1此时得移除与数组2相同的元素,避免list重复添加

实现代码

 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. 两个数组的交集 II

链接

350. 两个数组的交集 II

解题思路

与上一题不同的是,本题交集允许重复,所以建议使用map来解决问题,具体步骤为:

	1. 遍历nums1,若map中存在遍历元素,则该元素value+1,不存在则创建以遍历元素为key的映射
	2. 遍历nums2,这里设遍历元素名为key,若map中存在key,就将key存入list中,并将map中的key对应的value-1,注意当key的value等于0时移除map中的这个key,并list添加过多的key,造成错误。

实现代码

 public int[] intersect(int[] nums1, int[] nums2) {
    
    
        TreeMap<Integer, Integer> map = new TreeMap<>();
        for (int num : nums1) {
    
    
            if (map.containsKey(num)) {
    
    
                map.put(num, map.get(num) + 1);
            } else {
    
    
                map.put(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;
    }

猜你喜欢

转载自blog.csdn.net/shark_chili3007/article/details/108013066