Map和Set—数据结构

文章目录

  • 1.搜索
    • 1.1常见搜索方式
    • 1.2模型
  • 2.map
    • 2.1介绍
    • 2.2 Map.Entry
    • 2.3map的使用
    • 2.4遍历map
    • 2.5TreeMap和HashMap的区别
  • 3.set
    • 3.1介绍
    • 3.2set的使用
    • 3.3遍历set
    • 3.4 TreeSet和HashSet的不同
  • 4.搜索树
    • 4.1概念
    • 4.2实现
    • 4.3性能分析
  • 5.哈希表
    • 5.1查找数据
    • 5.2冲突的概念
    • 5.3冲突的避免方法
    • 5.4冲突的解决方法
    • 5.5实现(简单类型)
    • 5.6实现(泛型)

1.搜索

1.1常见搜索方式

(1)直接遍历:时间复杂度会达到O(N)
(2)二分查找:时间复杂度O(logN)
(3)Map和set:一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关

1.2模型

(1)k-v模型:map(key-value)
(2)k模型:set(key)

2.map

Map是一个接口类,该类没有继承自Collection,该类中存储的是结构的键值对,并且K一定是唯一的,不能重复

2.1介绍

  1. Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap
  2. Map中Key是唯一的,如果相同会被替代成最新的value,value是可以重复的
  3. 在Map中插入时,key不能为空,但是value可以为空
  4. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)
  5. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)
  6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入

2.2 Map.Entry

Map.Entry 是Map内部实现的用来存放键值对映射关系的内部类

public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        map.put("hello",2);
        map.put("world",4);
        map.put("school",6);
        System.out.println(map);//{world=4, school=6, hello=2}
        //通过map.entrySet遍历key和value
        Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
        for (Map.Entry<String,Integer> entry:entrySet){
            //key:world  val:4 key:school  val:6 key:hello  val:2
            System.out.print("key:"+entry.getKey()+"  val:"+entry.getValue()+" ");
        }
        System.out.println();
        //将键值对中的value替换为指定value
        for (Map.Entry<String,Integer> entry:entrySet){
            //4 6 2
            System.out.print(entry.setValue(entry.getValue())+" ");
        }
    }

2.3map的使用

public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        map.put("hello",2);
        map.put("world",4);
        map.put("school",6);//设置 key 对应的 value
        System.out.println(map);//{world=4, school=6, hello=2}
        //存储顺序和打印的顺序不同是由于他的底层是通过哈希函数计算的
        System.out.println(map.get("hello"));//2 返回 key 对应的 value
        System.out.println(map.getOrDefault("hello", 4));//2 返回 key 对应的 value,key 不存在,返回默认值
        System.out.println(map.remove("world"));//4 删除 key 对应的映射关系
        System.out.println(map.keySet());//[school, hello] 返回所有 key 的不重复集合
        System.out.println(map.values());//[6, 2] 返回所有 value 的可重复集合
        System.out.println(map.entrySet());//[school=6, hello=2] 返回所有的 key-value 映射关系
        System.out.println(map.containsKey("world"));//false 判断是否包含 key
        System.out.println(map.containsValue(2));//true 判断是否包含 value
    }

2.4遍历map

public static void main(String[] args){
        Map<String,Integer> map = new HashMap<>();
        map.put("hello",2);
        map.put("world",4);
        map.put("school",6);
        System.out.println(map);//{world=4, school=6, hello=2}
        //通过map.keySet遍历key和value
        for (String key:map.keySet()){
            //key:world value:4 key:school value:6 key:hello value:2
            System.out.print("key:"+key+" value:"+map.get(key)+" ");
        }
        System.out.println();
        //通过map.values遍历value
        for (Integer value:map.values()){
            //4 6 2
            System.out.print(value+" ");
        }
        System.out.println();
        //集合方式遍历key
        Set<String> set = map.keySet();
        System.out.println(set);//[world, school, hello]
        //集合方式遍历value
        Collection<Integer> collection = map.values();
        System.out.println(collection);//[4, 6, 2]
        //通过map.entrySet遍历key和value
        Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
        for (Map.Entry<String,Integer> entry:entrySet){
            //key:world  val:4 key:school  val:6 key:hello  val:2
            System.out.print("key:"+entry.getKey()+"  val:"+entry.getValue()+" ");
        }
        System.out.println();
        //Map.Entry<>迭代器
        Set<Map.Entry<String,Integer>> entrySet1 = map.entrySet();
        Iterator<Map.Entry<String,Integer>> iterator = entrySet1.iterator();
        while (iterator.hasNext()){
            Map.Entry<String,Integer> entry = iterator.next();
            //key:world  val:4 key:school  val:6 key:hello  val:2
            System.out.print("key:"+entry.getKey()+"  val:"+entry.getValue()+" ");
        }
        System.out.println();
        //java8特有的遍历方法
        map.forEach((key,value)->{
            //key:world value:4 key:school value:6 key:hello value:2
            System.out.print("key:"+key+" value:"+value+" ");
        });
    }

2.5TreeMap和HashMap的区别

Map和Set—数据结构_第1张图片

3.set

3.1介绍

  1. Set是继承自Collection的接口类,Set中只存储了Key,并且要求key唯一
  2. Set的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的
  3. Set最大的功能就是对集合中的元素进行去重
  4. 实现Set接口的常用类有TreeSet和HashSet
  5. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
  6. Set中不能插入null的key

3.2set的使用

public static void main(String[] args) {
        //set不能添加重复元素
        Set<String> set = new HashSet<>();
        set.add("hello");
        set.add("world");
        set.add("world");
        set.add("school");//添加元素
        System.out.println(set);//[world, school, hello]
        System.out.println(set.contains("hello"));//true
        System.out.println(set.contains("d"));//false 是否包含某个元素
        System.out.println(set.remove("hello"));//true;
        System.out.println(set.remove("d"));//false 删除某个元素
        System.out.println(set.size());//2 返回set中元素的个数
        System.out.println(set.isEmpty());//false 检测set是否为空
        List<String> list = new ArrayList<>();
        list.add("abc");
        list.add("def");
        list.add("fag");
        set.addAll(list);//将集合c中的元素添加到set中
        System.out.println(set);//[world, abc, def, school, fag]
        set.clear();//清空set
}

3.3遍历set

public static void main(String[] args) {
        //set不能添加重复元素
        Set<String> set = new HashSet<>();
        set.add("hello");
        set.add("world");
        set.add("school");//添加元素
        System.out.println(set);//[world, school, hello]
        //使用toArray(),将set中的元素转换为数组返回,对数组进行遍历
        Object[] array = set.toArray();
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");//world school hello
        }
        System.out.println();
        //使用toArray(T[] a),传递一个数组,把集合中的元素存储到数组中
        String[] array1 = new String[set.size()];
        String[] strings = set.toArray(array1);
        for (int i = 0; i < strings.length; i++) {
            System.out.print(strings[i]+" ");//world school hello
        }
        System.out.println();
        //使用迭代器遍历
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()){
            System.out.print(iterator.next()+" ");//world school hello
        }
    }

3.4 TreeSet和HashSet的不同

Map和Set—数据结构_第2张图片

4.搜索树

4.1概念

二叉搜索树(二叉排序树):每一棵树的左子树小于根结点,右子树大于根结点,也可以是一棵空树
Map和Set—数据结构_第3张图片

4.2实现

public class BinaryTree {
    static class TreeNode{
        public int val;
        public TreeNode left;
        public TreeNode right;
        public TreeNode(int val){
            this.val = val;
        }
    }
    public static TreeNode root;
    //查找二叉搜索树中是否包含某个元素
    public static boolean search(int val){
        if (root == null){
            return false;
        }
        TreeNode cur = root;
        while (cur!=null){
            //找到val
            if(cur.val==val){
                return true;
            }else if (cur.val>val){
                //大于val,在左子树找
                cur = cur.left;
            }else {
                //小于val,在左子树找
                cur = cur.right;
            }
        }
        return false;
    }
    //添加一个元素到二叉搜索树中
    public static boolean insert(int val) {
    //空树,直接插入到根结点
        if (root == null){
            root = new TreeNode(val);
        }
        TreeNode cur = root;
        TreeNode tmp = null;
        //找到插入位置的父亲结点
        while (cur!=null){
            if (cur.val > val){
                tmp = cur;
                cur = cur.left;
            }else if (cur.val < val){
                tmp = cur;
                cur = cur.right;
            }else {
                return false;
            }
        }
        if (tmp.val < val){
            tmp.right = new TreeNode(val);
        }else {
            tmp.left = new TreeNode(val);
        }
        return true;
    }

    //中序遍历二叉树
    public static void inorder(TreeNode root) {
        if (root == null){
            return;
        }
        inorder(root.left);
        System.out.print(root.val+" ");
        inorder(root.right);
    }
    //删除元素
    public static boolean remove(int key){
        if (root == null){
            return false;
        }
        TreeNode cur = root;
        TreeNode parent = null;
        //找到删除结点和要删除的结点的父节点
        while (cur != null){
            if (cur.val > key){
                parent = cur;
                cur = cur.left;
            }else if(cur.val < key){
                parent = cur;
                cur = cur.right;
            }else {
                removeNode(cur,parent);
                return true;
            }
        }
        return false;
    }
    public static void removeNode(TreeNode cur,TreeNode parent){
        if (cur.left == null){
            if (cur == root){
                root = root.right;
            }else if (parent.right == cur){
                parent.right = cur.right;
            }else {
            //parent.left == cur
                parent.left = cur.right;
            }
        }else if (cur.right == null){
            if (cur == root){
                root = root.left;
            }else if (parent.right == cur){
                parent.right = cur.left;
            }else {
            //parent.left == cur
                parent.left = cur.left;
            }
        }else {
        //cur.left!=null&&cur.right!=null
        //使用替代法删除,此处在右树找最小值
            TreeNode target = cur.right;
            TreeNode targetParent = cur;
            while (target.left != null){
                targetParent = target;
                target = target.left;
            }
            cur.val = target.val;
            if (target == targetParent.left){
                targetParent.left = target.right;
            }else {
                targetParent.right = target.right;
            }
        }
    }
}

4.3性能分析

(1)单分支的树:时间复杂度为N
(2)改进:AVL树(高度平衡的二叉树)、红黑树(RBTree)

5.哈希表

存储和取数据的时候都会遵守某种规则,称作哈希函数,哈希函数是可以自己设定的,构造出来的结构称哈希表(散列表),用该方法进行搜索不需要进行多次关键码的比较,因此搜索的效率比较高

5.1查找数据

(1)顺序查找:时间复杂度O(N)
(2)二分查找:时间复杂度O(logN)
(3)哈希表:增删查改的时间复杂度都可以达到O(1)

5.2冲突的概念

不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

5.3冲突的避免方法

冲突的发生是必然的,我们只能尽量降低冲突的发生频率
(1)设计合理的哈希函数
设计原则:哈希函数定义域必须包括需要存储的全部关键码;哈希函数计算出来的地址能均匀分布在整个空间中;哈希函数应该比较简单
(2)调节负载因子
a,散列表的负载因子=填入表中的元素的个数/散列表的长度
b,负载因子和冲突率的关系
Map和Set—数据结构_第4张图片
c,超过负载因子就需要扩容
d,想要降低冲突率就只能减少负载因子,想要减少负载因子就只能增加散列表的长度

5.4冲突的解决方法

(1)闭散列(开放定址法):空间利用率低
a,线性探测:放到冲突位置的下一个空的地方(不好删除;会把冲突元素放到一起)
b,二次探测:使用Hi = (H0+i^2)%m放到下一个空的地方(i是冲突的次数,H0是冲突的位置,m是散列表的长度)
(2)开散列(哈希桶/链地址法/开链法):把所有的元素挂到链表上,JDK1.8使用的是尾插法
a,解决:数组+链表+红黑树(树化是有条件的)
b,树化的条件:链表的长度超过8;数组的长度超过64
c,把红黑树变为链表的条件:树的结点为6
Map和Set—数据结构_第5张图片
d,哈希表的扩容要注意:重新哈希

5.5实现(简单类型)

public class HashBuck {
    static class Node{
        public int key;
        public int val;
        public Node next;
        public Node(int key,int val){
            this.key = key;
            this.val = val;
        }
    }
    public int usedSize;//存放数据个数
    public static final double FACTOR = 0.75;//负载因子
    public Node[] array;
    public HashBuck(){
        this.array = new Node[8];
    }
    //插入元素
    public boolean put(int key,int val){
        Node node = new Node(key,val);
        //1、找到要插入下标的位置
        int index = key % array.length;
        //2、遍历这个下标的链表,看看有没有一样的值
        Node cur = array[index];
        while (cur!=null){
            if (cur.key == key){
                cur.val = val;//更新val的值
                return false;
            }
            cur = cur.next;
        }
        //3、遍历完成当前链表的值,若没有相同的就开始插入
        cur = array[index];
        node.next = cur;
        array[index] = node;
        this.usedSize++;
        //4、存放元素之后,判断当前哈希桶中的负载因子是否超过默认的负载因子
        if (loadFactor() >= FACTOR){
            //5、扩容
            resize();
        }
        return true;
    }
    //扩容
    private void resize(){
        //1、更新数组长度,申请二倍数组长度
        Node[] tmp = new Node[this.array.length*2];
        //2、遍历原来的数组,将每个小标的节点的数据都进行重新哈希
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur!=null){
                Node curNext = cur.next;
                int newIndex = cur.key % tmp.length;//新的数组的下标
                cur.next = tmp[newIndex];
                tmp[newIndex] = cur;
                cur = curNext;
            }
        }
        array = tmp;
    }
    //得到负载因子的值
    private double loadFactor(){
        return this.usedSize*1.0/this.array.length;
    }
    //获取val的值
    public int get(int key){
        int index = key % array.length;
        Node cur = array[index];
        while (cur != null){
            if (cur.key == key){
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }
}
//测试
public static void main(String[] args) {
        HashBuck h = new HashBuck();
        h.put(1,1);
        h.put(2,2);
        h.put(3,3);
        h.put(6,4);
        h.put(14,14);
        h.put(17,17);
        h.put(24,24);
        System.out.println('a');//在此处打断点
    }

5.6实现(泛型)

HashMap:

class Person{
    public String id;
    public Person(String id) {
        this.id = id;
    }
    @Override
    public String toString() {
        return "person{" +
                "id='" + id + '\'' +
                '}';
    }
    //找到下标下和key一样的结点
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(id, person.id);
    }
    //找到下标的节点
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
public static void main(String[] args) {
        HashMap<Person,String> hashMap = new HashMap<>();
        Person p1 = new Person("12345");
        Person p2 = new Person("12345");
        hashMap.put(p1,"xian");
        System.out.println(hashMap.get(p2));//xian
    }

注意:
(1)以后我们自己定义的类一定要重写equals和HashCode,要知道HashCode一样,equals不一定一样,equals一样,HashCode一定一样
(2)HashSet的底层是一个HashMap(原码中HashSet存的元素作为HashMap的key,不能重复)
(3)map第一次添加元素的时候,容量才会赋予16

public class HashBuck2<k,v> {
    static class Node<k,v>{
        public k key;
        public v val;
        public Node<k,v> next;
        public Node(k key,v val){
            this.key = key;
            this.val = val;
        }
    }
    public Node<k,v>[] array = (Node<k,v>[])new Node[8];
    public static final double FACTOR = 0.75;//负载因子
    public int usedSize;
    public void put(k key,v val){
        Node<k,v> node = new Node<>(key,val);
        //1、找到要插入的下标位置
        int hash = key.hashCode();//将key变成一个整数
        int index = hash % array.length;
        //2、遍历这个下标的链表,看看有没有一样的值
        Node<k,v> cur = array[index];
        while (cur!=null){
            if (cur.key.equals(key)){
                cur.val = val;//更新val的值
            }
            cur = cur.next;
        }
        //3、遍历完成当前链表的值,若没有相同的就开始插入
        cur = array[index];
        node.next = cur;
        array[index] = node;
        this.usedSize++;
        //4、存放元素之后,判断当前哈希桶中的负载因子是否超过默认的负载因子
        if (loadFactor() >= FACTOR){
            //5、扩容
            resize();
        }
    }
    //扩容
    private void resize(){
        //1、更新数组长度,申请二倍数组长度
        Node<k,v>[] tmp = new Node[this.array.length*2];
        //2、遍历原来的数组,将每个小标的节点的数据都进行重新哈希
        for (int i = 0; i < array.length; i++) {
            Node<k,v> cur = array[i];
            while (cur!=null){
                Node<k,v> curNext = cur.next;
                int hash = array[i].hashCode();//将key变成一个整数
                int newIndex = hash % array.length;//新的数组的下标
                cur.next = tmp[newIndex];
                tmp[newIndex] = cur;
                cur = curNext;
            }
        }
        array = tmp;
    }
    //得到负载因子的值
    private double loadFactor(){
        return this.usedSize*1.0/this.array.length;
    }
    //获取val的值
    public v get(k key){
        int hash = key.hashCode();
        int index = hash % array.length;
        Node<k,v> cur = array[index];
        while (cur != null){
            if (cur.key.equals(key)){
                return cur.val;
            }
            cur = cur.next;
        }
        return null;
    }
}
//测试
public static void main(String[] args) {
        HashBuck2<Person,String> hashBuck2 = new HashBuck2<>();
        Person p1 = new Person("12345");
        Person p2 = new Person("12345");
        hashBuck2.put(p1,"xian");
        System.out.println(hashBuck2.get(p2));//xian
    }

你可能感兴趣的:(数据结构,数据结构,java,算法)