Come on, guys, let’s meet each other.

I am a worldly wanderer, a Java programming ape who has been wandering for many years

Having already introduced HashMap, today we’ll take a look at another subclass of Map: TreeMap

Front knowledge

Before we introduce TreeMap, let’s take a look at some of the basics

The sorting way

Before we get into sorting, let’s talk a little bit about what it is: ordered, unordered, sort

The orderly

Ensure that the order of insertion is the same as the order of storage in the container, typically:

  • List

A disorderly

Not inserted in the same order as stored in the container, typically:

  • Set
  • Map

The sorting

Output the order of elements that match the rule when iterating based on a rule, for example:

  • TreeMap
  • TreeSet

So let’s see how we sort this

So, there’s a requirement that we print the elements of the set in some order, so what do we do? In this way, Java provides us with two implementations:

Comparable

Implementing this interface requires an entity object and overriding it compareTo(). Let’s look at an example:

// Define a Student object
class Student implements Comparable<Student> {

    public int id;
    public String name;
    public int age;

    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    /** ** */
    @Override
    public int compareTo(Student o) {
        // Sort by age from youngest to oldest
        return age - o.age;
    }

    @Override
    public String toString(a) {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\' ' +
                ", age=" + age +
                '} '; }}/ / small case
ArrayList<Student> students = new ArrayList<Student>(6) {{
    add(new Student(1."Zhang".20));
    add(new Student(2."里斯".18));
    add(new Student(3."Fifty".38));
    add(new Student(4."Zhao Liu".10));
    add(new Student(5."The weather".77));
}};

// Output before sort
System.out.println("Output before sort :");
System.out.println(students);

System.out.println("= = = = = = = = = = = = = = = =");
// Sort operations
Collections.sort(students);
System.out.println("Sorted output :");
System.out.println(students);

[Student{id=1, name=' c ', age=20}, Student{id=2, name=' c ', age=18}, Student{id=3, name=' c ', age=38}, Student {id = 4, name = 'Zhao Liu', the age = 10}, Student {id = 5, name = 'weather', the age = 77}] = = = = = = = = = = = = = = = = [Student {id = 4, name = 'Zhao Liu', the age = 10}, Student {id = 2, name = 'rees, age = 18}, Student {id = 1, name =' zhang 'age = 20}, Student {id = 3, name =' Cathy ', the age = 38}, Student {id = 5, Name =' weather ', age=77}] **/
Copy the code

You can see that we’ve implemented sorting by age from youngest to oldest

One thing to note here is that in compareTo(), the object passed in is compared to the current object:

  • If the comparison is greater than 0, it is sorted in descending order
  • If the comparison is less than 0, it is sorted in ascending order
  • If the contrast is equal to 0, the current doesn’t change

Comparator

Is it written as an external class, or as the code above? Let’s change a few things:

Collections.sort(students, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        // Sort by descending ID
        return (int) (o2.id - o1.id); }});/** Output before sorting: [Student {id = 1, name = 'zhang' age = 20}, Student {id = 2, name = 'rees, age = 20}, Student {id = 3, name =' Cathy ', the age = 38}, Student {id = 4, Name = 'Zhao Liu, age = 10}, Student {id = 5, name =' weather ', the age = 77}] = = = = = = = = = = = = = = = = sorted output: [Student{id=5, name=' 1 ', age=77}, Student{id=4, name=' 2 ', age= 60}, Student{id=5, name=' 3 ', age= 60}, Student{id=5, name=' 3 ', age= 60}, Student{id=2, Name = 'rees, age = 20}, Student {id = 1, name =' zhang 'age = 20}] * * /
Copy the code

View that the requirements have been implemented

The comparison of returned values in compare() is the same as in the first method

We choose a reasonable sorting way according to the actual needs

The tree is introduced

Tree is a kind of data structure, it is composed of N (N >=1) finite nodes of a hierarchical relationship set. It’s called a tree because it looks like an upside-down tree, meaning it has roots up and leaves down. It has the following characteristics:

  • Each node has zero or more children;
  • Nodes that have no parents are called root nodes;
  • Each non-root node has one and only one parent;
  • In addition to the root, each child can be divided into multiple disjoint subtrees

Excerpt from: Baidu Encyclopedia: Tree

Binary tree

Binary tree is an important type of tree structure. It is one of the most commonly used tree structures in data structures. Each node has at most two child nodes

Binary search tree

As the name implies, binary search trees are organized by binary trees. Compared with binary trees, binary search trees have the following features:

  • Each node can have a maximum of two nodes
  • Insert element nodes using the rule of smaller on the left and larger on the right, if equal, to the right
  • So it can be used when traversing nodesdichotomyTo reduce the query of elements

Binary search tree insertion process

Balanced tree

Also known as AVL tree, is based on binary search tree is an extension, that is to say, has all the characteristics of binary search tree. Binary search trees have disadvantages:

  • Data insertion mode, easy to cause two sides of the node, one side long side short problem, so in the passdichotomyThere are also performance issues when iterating over elements

The AVL tree has been modified for this situation:

  • AVL tree will rotate the unbalanced tree, optimize the whole data structure, ensure the balance of the whole tree, and ensure the efficiency of the whole binary search
  • Rotation rule: The absolute value of the difference between the height of the left and right child nodes of each node is at most 1, that is, the balance factor is in the range [-1,1].

Red and black tree

Based on an evolution of balanced tree, there is also rotation operation to maintain the balance of binary tree, and on this basis, color changing operation is added, which has the following characteristics:

  • Nodes are red or black
  • The root node is black, and each leaf node (NUIL node) is black
  • If a node is red, its children are black (i.e., there can be no consecutive red nodes).
  • All paths from any node to each of its leaves contain the same number of black nodes
  • The maximum path is not more than twice the minimum path

Here is not explained in detail, simply say that there is a concept

Source code analysis TreeMap

Let’s take a look at TreeMap:

Some of the problems I mentioned earlier: We encountered a class where the most important thing was the annotation in the class. Let’s first look at how the annotation for TreeMap describes TreeMap:

  • Map based on red-black tree
  • Depending on the constructor we use, TreeMap is ordered and we can specify how to sort it
  • TreeMap is thread-unsafe, and you need to wrap TreeMap if you want to implement it
SortedMap m = Collections.synchronizedSortedMap(newTreeMap(...) );Copy the code

A constructor

Let’s take a look at how we use TreeMap

TreeMap<String, String> treeMap = new TreeMap<>();
new TreeMap<String, Long>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return 0; }});Copy the code
public TreeMap(a) {
    comparator = null;
}

public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}
Copy the code

The second constructor passes in a comparator, which we talked about in sorting, and see what it means to return

But a note of caution here: if we pass no comparator and default to NULL, then we need to understand:

  • The Key passed in must implement the type of the Comparable interface, which is explained in the ** PUT ()** method

put()

Before we look at this method, let’s look at a class:

static final class Entry<K.V> implements Map.Entry<K.V> {
    K key;
    V value;
    Entry<K,V> left;
    Entry<K,V> right;
    Entry<K,V> parent;
    boolean color = BLACK;

    Entry(K key, V value, Entry<K,V> parent) {
        this.key = key;
        this.value = value;
        this.parent = parent; }}Copy the code

We already know that TreeMap’s underlying structure is a red-black tree to store data, so the corresponding implementation in the code looks like this.

Now, how do we add elements

public V put(K key, V value) {
    Entry<K,V> t = root;
    / / the root node
    if (t == null) {
        compare(key, key); // type (and possibly null) check

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    
    // The comparator compares the parent node to which the current node belongs
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if(cpr ! =null) {
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while(t ! =null);
    }
    else {
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        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);
    }
    
    // Assign
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    
    // Change color, rotate
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}
Copy the code

To sum up, it can be divided into four steps:

  • Determines that if the current root node is null, the first element currently inserted is the root node element

  • If there is a root node, then the collator compares the element to verify whether the current element should be on the left or the right when the element is added. If the comparison is 0, the current element exists in TreeMap and is overwritten directly. This illustrates the point: In TreeMap, there are no duplicate elements

  • Find its own position, and then reference the pointer

  • Node discoloration operation and rotation operation

The first 3 points are simple, but do.. The while loop uses the collator to compare, and here’s the point I mentioned in the constructor:

class A {
    public Long id;
}

TreeMap<A, String> map = new TreeMap<>();
map.put(new A(), "11");
map.put(new A(), "11");
System.out.println(map);

// java.lang.ClassCastException: zopx.top.study.jav.maps.A cannot be cast to java.lang.Comparable
Copy the code

When the default constructor is used, there is an error in this approach: type conversion exceptions, which is why TreeMap: Key must be Comparable

Next we will focus on node discoloration and rotation operations

private void fixAfterInsertion(Entry<K,V> x) {
    // Mark the current node in red
    x.color = RED;

    // If there is an imbalance after the element is inserted, adjust it
    while(x ! =null&& x ! = root && x.parent.color == RED) {// Check if it is the left node
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if(x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); }}else {
            // Otherwise, the right node
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if(x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); }}}// The root node is always black
    root.color = BLACK;
}
Copy the code

Let me analyze the code by drawing it so that it is easier to understand:

This is the simplest example with very few nodes, so you can walk through the code and understand the logic of the code

Right hand operation

private void rotateRight(Entry<K,V> p) {
    if(p ! =null) {
        Entry<K,V> l = p.left;
        p.left = l.right;
        if(l.right ! =null) l.right.parent = p;
        l.parent = p.parent;
        if (p.parent == null)
            root = l;
        else if (p.parent.right == p)
            p.parent.right = l;
        elsep.parent.left = l; l.right = p; p.parent = l; }}Copy the code

Also, there are left-handed operations in the red-black tree, which you can see in the source code: rotateLeft(), which is very similar to right-handed operations

It is best to be able to analyze the source code and draw pictures to deepen your understanding

This is how TreeMap is implemented based on red black trees

Remove (Object key) method and put() method similar, you can see the source code

get()

Here’s a quick look at the get() method:

Entry<K,V> p = getEntry(key);

final Entry<K,V> getEntry(Object key) {
    // Offload comparator-based version for sake of performance
    if(comparator ! =null)
        return getEntryUsingComparator(key);
    if (key == null)
        throw new NullPointerException();
    
    // Default constructor
    @SuppressWarnings("unchecked")
    Comparable<? super K> k = (Comparable<? super K>) key;
    Entry<K,V> p = root;
    while(p ! =null) {
        // Continuously compare, if ==0, then this is the current required Entry
        int cmp = k.compareTo(p.key);
        if (cmp < 0)
            p = p.left;
        else if (cmp > 0)
            p = p.right;
        else
            return p;
    }
    return null;
}

// Custom sorting
final Entry<K,V> getEntryUsingComparator(Object key) {
    @SuppressWarnings("unchecked")
    K k = (K) key;
    Comparator<? super K> cpr = comparator;
    if(cpr ! =null) {
        Entry<K,V> p = root;
        while(p ! =null) {
            // Continuously compare, if ==0, then this is the current required Entry
            int cmp = cpr.compare(k, p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                returnp; }}return null;
}
Copy the code

This method is relatively simple, with the comparison done through the comparator in the while() loop

TreeMap more API methods

For more information on how to use TreeMap, see its documentation:

TreeMap API documentation

Data structure visualization site