集合和映射

本文探讨了集合和映射两种数据结构,详细介绍了基于二分搜索树和链表的集合实现,以及它们的增删查操作的时间复杂度。在映射方面,通过链表和二分搜索树展示了映射的实现方式,并分析了它们的效率。此外,还讨论了LeetCode中关于集合和映射的应用,例如词频统计和两个数组的交集问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

集合和映射

集合

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++2h1=2h1=n

h = l o g 2 ( n + 1 ) h=log_2(n+1) h=log2(n+1)

同样的数据可以对应不同的二分搜索树,二分搜索树可能退化成链表(最坏的情况):

按1,2,3,4,5,6的顺序构建二分搜索树,会导致最差的情况出现

image-20201023110537047

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

image-20201026211745555

针对最差的情况,引入平衡二叉树。

集合和映射的关系

若已有映射的底层实现,可直接实现集合:将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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值