zoukankan      html  css  js  c++  java
  • TreeMap 的排序冲突吗

    今天在网上看到一个问题:一个已经构建好的 TreeSet,怎么完成倒排序?

    网上给出的答案是:

    通过TreeSet构造函数传入一个比较器,指定比较器进行排序为原排序的倒叙。
    TreeSet的自然排序是根据集合元素的大小,TreeSet将他们以升序排列。如果需要实现定制排序,例如降序,则可以使用Comparator接口。该接口里包含一个int compare(T o1,T o2)方法,该方法用于比较o1和o2的大小。
    如果需要实现定制排序,则需要在创建TreeSet集合对象时,并提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。

    我们知道,如果要实现TreeSet 的 排序(或者说让一个TreeSet可用),必须让加入的对象具有可排序性,否则就会报错 java.lang.ClassCastException。

    实现思路有两个(二选一即可):

    1、加入的对象(相对于TreeMap,就是key对象,TreeSet 是通过 TreeMap 来实现的)需要实现 Comparable 接口

    package com.cd.demo;
    
    import java.util.Set;
    import java.util.TreeSet;
    
    public class TreeSetTest1 {
    
        public static void main(String[] args) {
            // 构造一个可用的TreeSet对象:传入对象实现 Comparable 接口 (按照年龄排序)
            Set<User> userSet = new TreeSet<>();
            userSet.add(new User(5, 32, "阿布1"));
            userSet.add(new User(12, 25, "阿布2"));
            userSet.add(new User(9, 36, "阿布3"));
            userSet.add(new User(20, 34, "阿布4"));
            userSet.add(new User(15, 48, "阿布5"));
            for (User u : userSet) {
                System.out.println(u.toString());
            }
        }
    
        // static 内部类,可以在main方法中 直接使用new User(...)初始化,否则就要:new TreeSetTest().new User(...) 才行
        static class User implements Comparable<User> {
            Integer id;
    
            Integer age;
    
            String name;
    
            public User(Integer id, int age, String name) {
                super();
                this.id = id;
                this.age = age;
                this.name = name;
            }
    
            public Integer getId() {
                return id;
            }
    
            public void setId(Integer id) {
                this.id = id;
            }
    
            public Integer getAge() {
                return age;
            }
    
            public void setAge(Integer age) {
                this.age = age;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            @Override
            public String toString() {
                return "User [id=" + id + ", age=" + age + ", name=" + name + "]";
            }
    
            @Override
            public int compareTo(User arg0) {
                return this.age.compareTo(arg0.getAge());
            }
        }
    
    }
    User [id=12, age=25, name=阿布2]
    User [id=5, age=32, name=阿布1]
    User [id=20, age=34, name=阿布4]
    User [id=9, age=36, name=阿布3]
    User [id=15, age=48, name=阿布5]
    

    2、TreeSet初始化时需要一个 Comparator 接口实例 的入参

    package com.cd.demo;
    
    import java.util.Comparator;
    import java.util.Set;
    import java.util.TreeSet;
    
    public class TreeSetTest2 {
    
        public static void main(String[] args) {
            // 构造一个可用的TreeSet对象:传入一个Comparator 接口实例作为入参 (按照id排序)
            Set<User> userSet = new TreeSet<>(new Comparator<User>() {
                @Override
                public int compare(User arg0, User arg1) {
                    return arg0.getId().compareTo(arg1.getId());
                }
            });
            // 因为 userSet 已经有入参 Comparator,故不会报错
            userSet.add(new User(5, 32, "阿布1"));
            userSet.add(new User(12, 25, "阿布2"));
            userSet.add(new User(9, 36, "阿布3"));
            userSet.add(new User(20, 34, "阿布4"));
            userSet.add(new User(15, 48, "阿布5"));
            for (User u : userSet) {
                System.out.println(u.toString());
            }
        }
    
        static class User {
            Integer id;
    
            Integer age;
    
            String name;
    
            public User(Integer id, int age, String name) {
                super();
                this.id = id;
                this.age = age;
                this.name = name;
            }
    
            public Integer getId() {
                return id;
            }
    
            public void setId(Integer id) {
                this.id = id;
            }
    
            public Integer getAge() {
                return age;
            }
    
            public void setAge(Integer age) {
                this.age = age;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            @Override
            public String toString() {
                return "User [id=" + id + ", age=" + age + ", name=" + name + "]";
            }
    
        }
    
    }
    User [id=5, age=32, name=阿布1]
    User [id=9, age=36, name=阿布3]
    User [id=12, age=25, name=阿布2]
    User [id=15, age=48, name=阿布5]
    User [id=20, age=34, name=阿布4]
    

     那么,现在问题来了,如果TreeSet中 保存的对象实现了 Comparable 接口,而 TreeSet 又传入了外部的 Comparator,会怎么样呢,会出现排序冲突吗?

    看代码:

    package com.cd.demo;
    
    import java.util.Comparator;
    import java.util.Set;
    import java.util.TreeSet;
    
    public class TreeSetTest {
    
        public static void main(String[] args) {
            // 构造一个可用的TreeSet对象:要么传入一个Comparator 接口实例作为入参,要么 key 必须实现 Comparable 接口
            // 现在同时两个都满足,User内部按照 age 排序,外部 Comparator 按照id 排序,会怎么样呢
            Set<User> userSet = new TreeSet<>(new Comparator<User>() {
                @Override
                public int compare(User arg0, User arg1) {
                    return arg0.getId().compareTo(arg1.getId());
                }
            });
            // 因为 userSet 已经有入参 Comparator,故不会报错
            userSet.add(new User(5, 32, "阿布1"));
            userSet.add(new User(12, 25, "阿布2"));
            userSet.add(new User(9, 36, "阿布3"));
            userSet.add(new User(20, 34, "阿布4"));
            userSet.add(new User(15, 48, "阿布5"));
            // userSet 构造测试(同时实现内外排序时,按照 Comparator 外部排序来) 
            for (User u : userSet) {
                System.out.println(u.toString());
            }
        }
    
        // static 内部类,可以在main方法中 直接使用new User(...)初始化,否则就要:new TreeSetTest().new User(...) 才行
        static class User implements Comparable<User> {
            Integer id;
    
            Integer age;
    
            String name;
    
            public User(Integer id, int age, String name) {
                super();
                this.id = id;
                this.age = age;
                this.name = name;
            }
    
            public Integer getId() {
                return id;
            }
    
            public void setId(Integer id) {
                this.id = id;
            }
    
            public Integer getAge() {
                return age;
            }
    
            public void setAge(Integer age) {
                this.age = age;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            @Override
            public String toString() {
                return "User [id=" + id + ", age=" + age + ", name=" + name + "]";
            }
    
            @Override
            public int compareTo(User arg0) {
                return this.age.compareTo(arg0.getAge());
            }
        }
    
    }
    User [id=5, age=32, name=阿布1]
    User [id=9, age=36, name=阿布3]
    User [id=12, age=25, name=阿布2]
    User [id=15, age=48, name=阿布5]
    User [id=20, age=34, name=阿布4]
    

      可以看出,如果同时实现了内外排序(对象实现 Comparable 接口、TreeSet 初始化 传入 Comparator 接口对象),则排序 以 Comparator 的外部排序进行

    看一下TreeSet 的 add 方法源码,内部其实调用的是TreeMap 的 put:

    public V put(K key, V value) {
      // 根节点
      Entry<K,V> t = root;
      // 如果根节点为空,则直接创建一个根节点,返回
      if (t == null) {
         // TBD:
         // 5045147: (coll) Adding null to an empty TreeSet should
         // throw NullPointerException
         //
         // compare(key, key); // type check
          root = new Entry<K,V>(key, value, null);
          size = 1;
          modCount++;
          return null;
      }
      // 记录比较结果
      int cmp;
      Entry<K,V> parent;
      // split comparator and comparable paths
      // 当前使用的比较器
      Comparator<? super K> cpr = comparator ;
      // 如果比较器不为空,就是用指定的比较器来维护TreeMap的元素顺序
      if (cpr != null) {
           // do while循环,查找key要插入的位置(也就是新节点的父节点是谁)
          do {
              // 记录上次循环的节点t
              parent = t;
              // 比较当前节点的key和新插入的key的大小
              cmp = cpr.compare(key, t. key);
               // 新插入的key小的话,则以当前节点的左孩子节点为新的比较节点
              if (cmp < 0)
                  t = t. left;
              // 新插入的key大的话,则以当前节点的右孩子节点为新的比较节点
              else if (cmp > 0)
                  t = t. right;
              else
            // 如果当前节点的key和新插入的key想的的话,则覆盖map的value,返回
                  return t.setValue(value);
          // 只有当t为null,也就是没有要比较节点的时候,代表已经找到新节点要插入的位置
          } while (t != null);
      }
      else {
          // 如果比较器为空,则使用key作为比较器进行比较
          // 这里要求key不能为空,并且必须实现Comparable接口
          if (key == null)
              throw new NullPointerException();
          Comparable<? super K> k = (Comparable<? super K>) key;
          // 和上面一样,喜欢查找新节点要插入的位置
          do {
              parent = t;
              cmp = k.compareTo(t. key);
              if (cmp < 0)
                  t = t. left;
              else if (cmp > 0)
                  t = t. right;
              else
                  return t.setValue(value);
          } while (t != null);
      }
      // 找到新节点的父节点后,创建节点对象
      Entry<K,V> e = new Entry<K,V>(key, value, parent);
      // 如果新节点key的值小于父节点key的值,则插在父节点的左侧
      if (cmp < 0)
          parent. left = e;
      // 如果新节点key的值大于父节点key的值,则插在父节点的右侧
      else
          parent. right = e;
      // 插入新的节点后,为了保持红黑树平衡,对红黑树进行调整
      fixAfterInsertion(e);
      // map元素个数+1
      size++;
      modCount++;
      return null;
    }

    注意看上面的下划线部分代码,jdk 中 默认是先使用 Comparator ,如果没有 Comparator ,才使用对象的 Comparable 接口。

    那回到开篇的问题,如果需要实现倒序,就得分两种情况了:

    1、只实现了 Comparable 的倒序;

    2、实现了 Comparator 的倒序。

    这两种情况,都可以通过 重新 建立一个 TreeSet,传入倒序的 Comparator 来实现倒序,伪代码为:

            Set<User> userSetNew = new TreeSet<>(new Comparator<User>() {
                @Override
                public int compare(User arg0, User arg1) {
                    return ...; // 新的排序逻辑
                }
            });
            userSetNew.addAll(userSetOld);
            System.out.println("---------------  倒序后  ----------------");
            for (User u : userSetNew ) {
                System.out.println(u.toString());
            }

    这个思路有一种投机取巧的方法,就是 compare 方法中默认返回 -1,比如上面的代码实现倒序可以为:

    package com.cd.demo;
    
    import java.util.Comparator;
    import java.util.Set;
    import java.util.TreeSet;
    
    public class TreeSetTest {
    
        public static void main(String[] args) {
            // 构造一个可用的TreeSet对象:要么传入一个Comparator 接口实例作为入参,要么 key 必须实现 Comparable 接口
            // 现在同时两个都满足,User内部按照 age 排序,外部 Comparator 按照id 排序,会怎么样呢
            Set<User> userSet = new TreeSet<>(new Comparator<User>() {
                @Override
                public int compare(User arg0, User arg1) {
                    return arg0.getId().compareTo(arg1.getId());
                }
            });
            // 因为 userSet 已经有入参 Comparator,故不会报错
            userSet.add(new User(5, 32, "阿布1"));
            userSet.add(new User(12, 25, "阿布2"));
            userSet.add(new User(9, 36, "阿布3"));
            userSet.add(new User(20, 34, "阿布4"));
            userSet.add(new User(15, 48, "阿布5"));
            // userSet 构造测试(同时实现内外排序时,按照 Comparator 外部排序来) 
            for (User u : userSet) {
                System.out.println(u.toString());
            }
    
            Set<User> userSet2 = new TreeSet<>(new Comparator<User>() {
                @Override
                public int compare(User arg0, User arg1) {
                    return -1;
                }
            });
            userSet2.addAll(userSet);
            System.out.println("---------------  倒序后  ----------------");
            for (User u : userSet2) {
                System.out.println(u.toString());
            }
        }
    
        // static 内部类,可以在main方法中 直接使用new User(...)初始化,否则就要:new TreeSetTest().new User(...) 才行
        static class User implements Comparable<User> {
            Integer id;
    
            Integer age;
    
            String name;
    
            public User(Integer id, int age, String name) {
                super();
                this.id = id;
                this.age = age;
                this.name = name;
            }
    
            public Integer getId() {
                return id;
            }
    
            public void setId(Integer id) {
                this.id = id;
            }
    
            public Integer getAge() {
                return age;
            }
    
            public void setAge(Integer age) {
                this.age = age;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            @Override
            public String toString() {
                return "User [id=" + id + ", age=" + age + ", name=" + name + "]";
            }
    
            @Override
            public int compareTo(User arg0) {
                return this.age.compareTo(arg0.getAge());
            }
        }
    
    }
    User [id=5, age=32, name=阿布1]
    User [id=9, age=36, name=阿布3]
    User [id=12, age=25, name=阿布2]
    User [id=15, age=48, name=阿布5]
    User [id=20, age=34, name=阿布4]
    ---------------  倒序后  ----------------
    User [id=20, age=34, name=阿布4]
    User [id=15, age=48, name=阿布5]
    User [id=12, age=25, name=阿布2]
    User [id=9, age=36, name=阿布3]
    User [id=5, age=32, name=阿布1]
    

      

  • 相关阅读:
    test
    有偏估计和无偏估计
    Spark Shuffle
    Adaboost算法推导
    Spark优化 – 基础篇
    决策树 – 回归
    HBase的文件合并(minor/major compact)
    HBase的列式存储
    centos7配置固定ip
    Generate a Certificate Signing Request (CSR) in macOS Keychain Access
  • 原文地址:https://www.cnblogs.com/klbc/p/10546530.html
Copyright © 2011-2022 走看看