ma'am audrey's lessons

ITC 120 › Midterm › Balanced trees

Balanced trees · Lesson 2 of 2

Red-Black Trees

This lesson teaches:Data structureAlgorithm

Another self-balancing BST. Instead of measuring heights, it paints each node red or black and follows a few color rules.

By the end, you can:

  1. state the five rules, and say whether a colored tree follows them (and which rule it breaks);
  2. explain why the rules keep the longest path at most twice as long as the shortest;
  3. fix a red-red problem after an insertion: check the uncle, then recolor or rotate (line or triangle);
  4. insert a list of values into a red-black tree by hand, showing every fix;
  5. match each part of the Java insertion code to a step of the procedure;
  6. write the rotateRight method for the given code and check it with the built-in tests.

Scope: this lesson covers insertion only. Deleting from a red-black tree uses a different, longer fix with more cases, and it is not covered here.

You need: the AVL trees lesson (especially rotations and the LL, RR, LR, RL shapes), scratch paper, and a pen (a red and a black pen help a lot). For Part 8: a phone or computer with internet.

Part 1 · The idea

Balancing with colors

In the AVL lesson, the tree stayed balanced by checking heights after every insert. A red-black tree solves the same problem in a different way: each node stores just one extra piece of information, its color (red or black).

Red-black tree

A red-black tree is a binary search tree in which every node is colored red or black, and the colors follow five rules. When an insertion breaks a rule, the tree fixes it by recoloring nodes and, if needed, rotating.

481216202428

A red-black tree. Still a BST: in-order gives 4, 8, 12, 16, 20, 24, 28.

Think of lining up

Imagine a school rule for lining up: the first student in every line wears black, no two "red-shirt" students may stand next to each other, and every line must have the same number of "black-shirt" students. With those rules, no line can get much longer than the others. The shortest a line can be is all black. The longest goes black, red, black, red..., with at most one red after each black, so it is at most twice as long.

Shortest line: all black, 3 studentsfrontBBBLongest line: 6 students = 2 × 3frontBRBRBRNot allowed: two reds side by sidefrontBRRB
Both allowed lines have 3 black shirts. The longest line can add only one red after each black, so it has at most 6 students: twice the shortest. In a red-black tree, each line is a path from the root down, and the shirts are the node colors (the black first student is the black root).

Where you see this in IT

Java's built-in TreeMap class is a red-black tree, and TreeSet is built on a TreeMap. You will use them in the Sets and Maps lessons. Every time you add a key, Java does the recoloring and rotations from this lesson for you.

Part 2 · The rules

The five rules

Every red-black tree follows these five rules at all times. Rules 4 and 5 are the important ones; the others set things up.

  1. Color rule. Every node is either red or black.
  2. Root rule. The root is black.
  3. NIL rule. Every empty spot (a null child, called a NIL leaf) counts as black. See "What is NIL?" below.
  4. Red rule. A red node cannot have a red child. No two reds in a row.
  5. Black rule. From any node, every path down to a NIL leaf passes through the same number of black nodes.

What is NIL?

NIL means "nothing here." It is an empty child spot: a place where a child could be, but there is none. In Java, it is a null link, like node.left == null.

Every node has two child spots, so the Part 1 tree, with 7 nodes, has 14 spots. Real nodes fill 6 of them, so 8 are NIL. You will see all 8 drawn as small black boxes in the picture below.

Rule 3 treats every NIL spot as a tiny black leaf for two reasons. First, every path down then ends at the same kind of thing, so we can count black nodes the same way on every path (rule 5). Second, when a node's uncle is missing, the uncle counts as black (you will need this in Part 3).

Careful with the word "leaf": in earlier lessons, a leaf is a real node with no children, like 4, 20, and 28 here. A NIL leaf is one of the empty spots below a node. In the red-black rules, "leaf" always means NIL leaf.

Seeing rule 5

Here is the same tree with its NIL leaves drawn as small black boxes. Start at the root, 12, and walk down to any box. Count the black circles you pass (do not count the box):

481216202428
Path from 12Black nodes passed
12 → 8 → 4 → NIL12, 8 = 2
12 → 8 → NIL12, 8 = 2
12 → 24 → 16 → 20 → NIL12, 16 = 2
12 → 24 → 28 → NIL12, 28 = 2

Every path has 2 black nodes. Red nodes are "free": they do not count. This shared number is called the black-height.

Why the rules keep it balanced

The shortest possible path is all black. The longest possible path alternates black, red, black, red (rule 4 stops two reds in a row). Both have the same number of blacks (rule 5). So the longest path is at most twice as long as the shortest. The tree can lean a little, but it can never become a chain.

Your turn: valid or not?

Check each tree against the rules. Tap your answer to see the reason.

Tree 1

102030

Tree 2

102030

Tree 3

10202530

Tree 4

102030

Tree 5

102030

Tree 6

5102030

Exam traps

  • Colors do not have to alternate. Black under black is fine (see Tree 4).
  • When counting black nodes for rule 5, count the same way on every path: either always include the starting node or never.
  • A missing child is a black NIL leaf. This matters when you look at the uncle.

Part 3 · The fix

Inserting: recolor or rotate

Every insertion starts the same way: insert the value like a normal BST, and color it red.

Why red? A red node does not change any black count, so rule 5 stays safe. The only rule that can break is rule 4: if the new node's parent is also red, we have a red-red problem. (One exception: in an empty tree, the new node is the root, so just color it black for rule 2.)

Meet the family

To fix a red-red problem, we look at the new node's relatives. These are the names we will use:

NameWho it is
xthe node we are fixing (at first, the new node)
parentx's parent
grandparentthe parent's parent
unclethe parent's sibling (the grandparent's other child)
10203040

New node 10 (x), parent 20, grandparent 30, uncle 40.

The decision

The fix depends on two questions. There are three types of situations:

SituationWhat to do
Parent is blackNothing. No rule is broken.
Parent red, uncle redRecolor: parent and uncle → black, grandparent → red. Then check the grandparent (it is red now, so its parent might also be red). If the grandparent is the root, just make it black.
Parent red, uncle black (or NIL)Rotate, using the same shapes as AVL:
Line (LL or RR): 1 rotation at the grandparent, then swap the colors of parent and grandparent.
Triangle (LR or RL): first rotate at the parent to make a line, then do the line fix.

How to remember

Red uncle → recolor. Black uncle → rotate.

After rotating, the node on top becomes black and its two children are red. In insertion, after a rotation, you are done.

Watch each situation

Pick one, then tap Next step. Watch the uncle (dashed ring) to decide: recolor or rotate?

    Color key
    red black new / just changed red-red problem uncle / grandparent

    Exam traps

    • Look at the uncle, not the sibling of the new node.
    • Recoloring can move the problem up the tree. Keep checking until the parent is black or you reach the root.
    • Don't forget the last step: the root must end up black.

    Part 4 · Putting it together

    Inserting, step by step

    This is the full procedure. Use it for every value, in order:

    1. Insert the value like a normal BST and color it red. (If the tree was empty, it is the root: color it black.)
    2. Check the parent. If it is black, you are done.
    3. If the parent is red, check the uncle.
      • Red uncle: recolor, then repeat step 2 from the grandparent.
      • Black uncle: name the shape (line or triangle), rotate, swap colors. Done.
    4. Finally, make sure the root is black.

    Worked example

    This list shows all three situations from the table, with both shapes (a triangle at 25 and a line at 60), including a recolor that moves the problem up the tree (watch 60). Turn on NIL leaves to see why an empty uncle counts as black. Then try your own numbers.

      Color key
      red black new / just changed red-red problem uncle / grandparent

      Doing it on paper

      • Use two pens, or write R or B beside each node. Circle the red ones.
      • Redraw the tree after every rotation. Recolors can be done by crossing out the letter.
      • Before fixing, write what you see: for example, "60: parent 70 red, uncle 90 red → recolor".
      • At the end, check rules 2, 4, and 5 on your final tree.

      Part 5 · Your turn

      Try it on paper

      Do each problem on paper before opening the answer. Afterwards, type the same numbers into the lab in Part 4 to replay it step by step.

      Problem 1

      Insert these values, in order, into an empty red-black tree:

      21, 9, 15, 5, 30, 26, 23

      Show the colors in your final tree. Then write its pre-order traversal.

      Show the answer
      591521232630

      What happened at each insert (values not listed needed no fix):

      • 21: becomes the root, colored black.
      • 15: uncle is black, triangle (LR) → 2 rotations + swap colors.
      • 5: uncle is red → recolor; then root turned red, so color it black.
      • 26: uncle is black, triangle (RL) → 2 rotations + swap colors.
      • 23: uncle is red → recolor.

      These are the same numbers as Problem 1 in the AVL lesson. Compare the two final trees: same shape here, but now with colors.

      Pre-order15, 9, 5, 26, 21, 23, 30
      In-order5, 9, 15, 21, 23, 26, 30

      Problem 2

      Insert these values, in order, into an empty red-black tree:

      40, 30, 50, 20, 25, 10, 5

      Show the colors in your final tree. Then write its pre-order traversal.

      Show the answer
      5102025304050

      What happened at each insert (values not listed needed no fix):

      • 40: becomes the root, colored black.
      • 20: uncle is red → recolor; then root turned red, so color it black.
      • 25: uncle is black, triangle (LR) → 2 rotations + swap colors.
      • 10: uncle is red → recolor.
      • 5: uncle is black, line (LL) → 1 rotation + swap colors.
      Pre-order40, 25, 10, 5, 20, 30, 50
      In-order5, 10, 20, 25, 30, 40, 50

      Problem 3

      Insert these values, in order, into an empty red-black tree:

      1, 2, 3, 4, 5, 6, 7, 8

      Show the colors in your final tree. Then write its pre-order traversal.

      Show the answer
      12345678

      What happened at each insert (values not listed needed no fix):

      • 1: becomes the root, colored black.
      • 3: uncle is black, line (RR) → 1 rotation + swap colors.
      • 4: uncle is red → recolor; then root turned red, so color it black.
      • 5: uncle is black, line (RR) → 1 rotation + swap colors.
      • 6: uncle is red → recolor.
      • 7: uncle is black, line (RR) → 1 rotation + swap colors.
      • 8: uncle is red → recolor; then uncle is black, line (RR) → 1 rotation + swap colors.

      Sorted input again. In a plain BST this would be a chain of height 7. Here the height is only 3.

      Pre-order4, 2, 1, 3, 6, 5, 7, 8
      In-order1, 2, 3, 4, 5, 6, 7, 8

      Part 6 · Side by side

      AVL vs. red-black

      Both are self-balancing binary search trees, and both use the same rotations. They differ in how strict they are. Here are the main differences:

      AVL treeRed-black tree
      Extra info per nodeheight (or balance factor)one color: red or black
      Balance promiseleft and right heights differ by at most 1longest path is at most 2× the shortest
      Shapemore tightly balanced (shorter)a little looser (can be a bit taller)
      Search, insert, deleteabout log₂ n steps (see How fast?)about log₂ n steps (the height can be up to twice as tall as AVL's)
      How it fixesrotationsrecoloring first; rotations only when the uncle is black
      Good forlots of searching, fewer changeslots of inserts and deletes

      AVL: 1 to 8

      12345678

      Red-black: 1 to 8

      12345678

      Same input, both balanced. They happen to have the same shape here, but that is not always true: for other inputs the red-black tree can be a little taller.

      Part 7 · In code

      Red-black insertion in Java

      The red-black code is longer than AVL's, because each node needs a link back to its parent (to find the grandparent and uncle). Read it alongside ; you will see each step.

      1. The node and a color helper

      static final boolean RED = true;    // final: never changes
      static final boolean BLACK = false; // (a named constant)
      
      static class RBNode {  // static: used by static methods
        int data;
        boolean color;
        RBNode left, right;
        RBNode parent;     // NEW: we need to go back up
      
        RBNode(int value) {
          data = value;
          color = RED;     // new nodes are always red
        }
      }
      
      static RBNode root = null;
      
      // an empty spot (null) counts as black
      static boolean isRed(RBNode node) {
        return node != null && node.color == RED;
      }

      All the pieces in this part go inside one class (in Part 8 it is called Main). RBNode is a static class so that the static methods can make nodes with new RBNode(value). Without static, Java stops with "non-static variable this cannot be referenced from a static context".

      2. Insert like a BST, then fix

      static void insert(int value) {
        // Step 1: normal BST insert
        RBNode parent = null;
        RBNode current = root;
        while (current != null) {
          parent = current;
          if (value < current.data) {
            current = current.left;
          } else if (value > current.data) {
            current = current.right;
          } else {
            return;               // duplicate: do nothing
          }
        }
        RBNode x = new RBNode(value);   // red
        x.parent = parent;
        if (parent == null) {
          root = x;
        } else if (value < parent.data) {
          parent.left = x;
        } else {
          parent.right = x;
        }
        fixUp(x);                 // Step 2: fix the colors
      }

      3. The fix: recolor or rotate

      static void fixUp(RBNode x) {
        while (isRed(x.parent)) {       // red-red problem
          RBNode p = x.parent;
          RBNode g = p.parent;          // grandparent
          if (p == g.left) {
            RBNode u = g.right;         // uncle
            if (isRed(u)) {             // uncle red: recolor
              p.color = BLACK;
              u.color = BLACK;
              g.color = RED;
              x = g;                    // check g next
            } else {                    // uncle black: rotate
              if (x == p.right) {       // triangle (LR)
                rotateLeft(p);
                x = p;
                p = x.parent;
              }
              rotateRight(g);           // line (LL)
              p.color = BLACK;
              g.color = RED;
            }
          } else {                      // mirror image
            RBNode u = g.left;
            if (isRed(u)) {
              p.color = BLACK;
              u.color = BLACK;
              g.color = RED;
              x = g;
            } else {
              if (x == p.left) {        // triangle (RL)
                rotateRight(p);
                x = p;
                p = x.parent;
              }
              rotateLeft(g);            // line (RR)
              p.color = BLACK;
              g.color = RED;
            }
          }
        }
        root.color = BLACK;             // rule 2
      }

      Compare with : while (isRed(x.parent)) is "red-red problem", isRed(u) is "uncle red", and the else part is "uncle black: rotate". The last line is rule 2.

      4. Rotation, with parent links

      static void rotateLeft(RBNode z) {
        RBNode y = z.right;             // y moves up
        z.right = y.left;               // moved child
        if (y.left != null) {
          y.left.parent = z;
        }
        y.parent = z.parent;            // y takes z's spot
        if (z.parent == null) {
          root = y;
        } else if (z == z.parent.left) {
          z.parent.left = y;
        } else {
          z.parent.right = y;
        }
        y.left = z;                     // z goes down, left
        z.parent = y;
      }
      // rotateRight is the mirror image:
      // swap every "left" and "right" above.
      // Writing it is your coding task (Part 8).

      This is the same left rotation as in the AVL lesson. The extra lines keep every parent link correct, and update root if the rotation happens at the top.

      Part 8 · Your coding task

      Write rotateRight and test it

      Part 7 showed the whole insertion code except one method: rotateRight. Your task is to write it. Everything else, including the tests, is given to you in one file.

      Why this method? Every line fix (LL) and both triangle fixes (LR and RL) use it, so the tests can only pass when it is right. It is also the part that is new compared with AVL: a rotation must keep every parent link correct, and the tests check every link.

      1. Prepare on paper

      Do these three things on paper first. They make the code much easier to write.

      1. Insert 30, 20, 10. Before any fix, the tree is a line: 30, then 20 as its left child, then 10 as 20's left child. Draw it, then draw it again after rotate right at 30.
      2. Go through line by line, and write the mirror of each line: swap every left and right.
      3. Trace your lines on your drawing. At the end, check every node: does its parent point to the node just above it? Does root point to the top node?

      2. Run the starter file

      Next, run the file once without changing it:

      1. Open any free online Java compiler, for example Programiz or OneCompiler. It works in a phone browser.
      2. Tap Copy the whole file, paste it into the compiler (replace the code already there), and tap Run. The class is called Main, as these sites expect.
      3. You should see 2 of 8 tests passed. The other tests fail with "rotateRight is not written yet". That is expected.
      Show the starter file (307 lines)
      // ITC 120 · Red-black tree insertion · coding task
      // 1. Copy this whole file into an online Java
      //    compiler. The class must be called Main.
      // 2. Press Run once first. Some tests FAIL: that
      //    is expected, because rotateRight is missing.
      // 3. Find "YOUR TASK", write rotateRight, and run
      //    again until every test says PASS.
      public class Main {
      
        static final boolean RED = true;    // final: never changes
        static final boolean BLACK = false; // (a named constant)
      
        static class RBNode {  // static: used by static methods
          int data;
          boolean color;
          RBNode left, right;
          RBNode parent;     // NEW: we need to go back up
      
          RBNode(int value) {
            data = value;
            color = RED;     // new nodes are always red
          }
        }
      
        static RBNode root = null;
      
        // an empty spot (null) counts as black
        static boolean isRed(RBNode node) {
          return node != null && node.color == RED;
        }
      
        static void insert(int value) {
          // Step 1: normal BST insert
          RBNode parent = null;
          RBNode current = root;
          while (current != null) {
            parent = current;
            if (value < current.data) {
              current = current.left;
            } else if (value > current.data) {
              current = current.right;
            } else {
              return;               // duplicate: do nothing
            }
          }
          RBNode x = new RBNode(value);   // red
          x.parent = parent;
          if (parent == null) {
            root = x;
          } else if (value < parent.data) {
            parent.left = x;
          } else {
            parent.right = x;
          }
          fixUp(x);                 // Step 2: fix the colors
        }
      
        static void fixUp(RBNode x) {
          while (isRed(x.parent)) {       // red-red problem
            RBNode p = x.parent;
            RBNode g = p.parent;          // grandparent
            if (p == g.left) {
              RBNode u = g.right;         // uncle
              if (isRed(u)) {             // uncle red: recolor
                p.color = BLACK;
                u.color = BLACK;
                g.color = RED;
                x = g;                    // check g next
              } else {                    // uncle black: rotate
                if (x == p.right) {       // triangle (LR)
                  rotateLeft(p);
                  x = p;
                  p = x.parent;
                }
                rotateRight(g);           // line (LL)
                p.color = BLACK;
                g.color = RED;
              }
            } else {                      // mirror image
              RBNode u = g.left;
              if (isRed(u)) {
                p.color = BLACK;
                u.color = BLACK;
                g.color = RED;
                x = g;
              } else {
                if (x == p.left) {        // triangle (RL)
                  rotateRight(p);
                  x = p;
                  p = x.parent;
                }
                rotateLeft(g);            // line (RR)
                p.color = BLACK;
                g.color = RED;
              }
            }
          }
          root.color = BLACK;             // rule 2
        }
      
        static void rotateLeft(RBNode z) {
          RBNode y = z.right;             // y moves up
          z.right = y.left;               // moved child
          if (y.left != null) {
            y.left.parent = z;
          }
          y.parent = z.parent;            // y takes z's spot
          if (z.parent == null) {
            root = y;
          } else if (z == z.parent.left) {
            z.parent.left = y;
          } else {
            z.parent.right = y;
          }
          y.left = z;                     // z goes down, left
          z.parent = y;
        }
        // rotateRight is the mirror image:
        // swap every "left" and "right" above.
        // Writing it is your coding task (Part 8).
      
        // ===== YOUR TASK: rotateRight =====
        // The mirror image of rotateLeft above.
        // Delete the "throw" line, then write the code.
        static void rotateRight(RBNode z) {
          throw new UnsupportedOperationException(
              "rotateRight is not written yet");
        }
      
        // ===== Tests: no need to change below =====
        static int passed = 0, total = 0;
        static boolean stopped = false;
      
        // Tree as text: 20B(10R,30R) means 20 is black,
        // its left child is 10 (red), its right is 30 (red).
        // "-" is an empty spot.
        static String text(RBNode n) {
          if (n == null) return "-";
          String s = n.data + (isRed(n) ? "R" : "B");
          if (n.left == null && n.right == null) return s;
          return s + "(" + text(n.left) + ","
                   + text(n.right) + ")";
        }
      
        // Checks rules 2, 4, 5 and every parent link.
        // Returns null if all is well, or what is wrong.
        static String why;
        static String problems() {
          why = null;
          if (isRed(root)) return "the root is red";
          blacks(root, null);
          return why;
        }
        static int blacks(RBNode n, RBNode parent) {
          if (n == null) return 1;          // NIL is black
          if (n.parent != parent && why == null)
            why = "wrong parent link at " + n.data;
          if (isRed(n) && isRed(parent) && why == null)
            why = "red-red at " + parent.data
                + " and " + n.data;
          int a = blacks(n.left, n);
          int b = blacks(n.right, n);
          if (a != b && why == null)
            why = "black counts differ below " + n.data;
          return a + (isRed(n) ? 0 : 1);
        }
      
        // Inserts the values one by one and checks the
        // tree after each one. Stops after 2 seconds, in
        // case of an endless loop. Returns null if all
        // went well, or what went wrong.
        static String tryInsert(int[] values) {
          root = null;
          String[] error = { null };
          Thread t = new Thread(() -> {
            try {
              for (int v : values) {
                insert(v);
                String p = problems();
                if (p != null) {
                  error[0] = p + " (right after inserting "
                           + v + ")";
                  return;
                }
              }
            } catch (StackOverflowError e) {
              error[0] = "the links form a loop (a node"
                       + " points back up to its ancestor)";
            } catch (Throwable e) {
              error[0] = e.toString();
            }
          });
          t.setDaemon(true);
          t.start();
          try {
            t.join(2000);
          } catch (InterruptedException e) { }
          if (t.isAlive()) {
            stopped = true;
            return "still running after 2 seconds"
                 + " (endless loop?). Tests stopped.";
          }
          return error[0];
        }
      
        static void test(String name, int[] values,
                         String expected) {
          if (stopped) return;
          total++;
          String error = tryInsert(values);
          String got = error == null ? text(root) : "";
          String bad = error != null ? error
              : !got.equals(expected) ? "wrong tree" : null;
          if (bad == null) {
            passed++;
            System.out.println("PASS  " + name);
          } else {
            System.out.println("FAIL  " + name + ": " + bad);
            if (error == null) {
              System.out.println("      expected " + expected);
              System.out.println("      got      " + got);
            }
          }
        }
      
        // 300 random lists, checked against the rules
        // and against Java's own TreeSet.
        static void randomTest() {
          if (stopped) return;
          total++;
          java.util.Random r = new java.util.Random(120);
          for (int t = 0; t < 300; t++) {
            int[] values = new int[r.nextInt(30)];
            java.util.TreeSet<Integer> set =
                new java.util.TreeSet<>();
            for (int i = 0; i < values.length; i++) {
              values[i] = r.nextInt(60);
              set.add(values[i]);
            }
            String bad = tryInsert(values);
            if (bad == null) {
              StringBuilder got = new StringBuilder();
              inOrder(root, got);
              StringBuilder want = new StringBuilder();
              for (int v : set) want.append(v).append(',');
              if (!got.toString().equals(want.toString()))
                bad = "in-order is not sorted, or values"
                    + " are missing";
            }
            if (bad != null) {
              System.out.println("FAIL  300 random lists: "
                  + bad);
              System.out.println("      list: "
                  + java.util.Arrays.toString(values));
              return;
            }
          }
          passed++;
          System.out.println("PASS  300 random lists");
        }
        static void inOrder(RBNode n, StringBuilder sb) {
          if (n == null) return;
          inOrder(n.left, sb);
          sb.append(n.data).append(',');
          inOrder(n.right, sb);
        }
      
        // Prints the tree for your own numbers.
        static void show(int[] values) {
          String error = tryInsert(values);
          System.out.println();
          System.out.println("Your numbers: "
              + java.util.Arrays.toString(values));
          if (error != null) {
            System.out.println("Error: " + error);
            return;
          }
          System.out.println("Tree: " + text(root));
          System.out.println("All rules hold.");
        }
      
        public static void main(String[] args) {
          test("Uncle red: recolor  (20, 10, 30, 5)",
               new int[] {20, 10, 30, 5}, "20B(10B(5R,-),30B)");
          test("Line RR  (10, 20, 30)",
               new int[] {10, 20, 30}, "20B(10R,30R)");
          test("Line LL  (30, 20, 10)",
               new int[] {30, 20, 10}, "20B(10R,30R)");
          test("Triangle LR  (30, 10, 20)",
               new int[] {30, 10, 20}, "20B(10R,30R)");
          test("Triangle RL  (10, 30, 20)",
               new int[] {10, 30, 20}, "20B(10R,30R)");
          test("Worked example (Part 4)",
               new int[] {5, 45, 25, 10, 35, 75, 70, 90, 60},
               "45B(25R(5B(-,10R),35B),75R(70B(60R,-),90B))");
          test("Problem 2 (Part 5)",
               new int[] {40, 30, 50, 20, 25, 10, 5},
               "40B(25R(10B(5R,20R),30B),50B)");
          randomTest();
          System.out.println();
          System.out.println(passed + " of " + total
              + " tests passed.");
      
          // Try your own numbers: change the list below.
          show(new int[] {50, 40, 30, 20, 10});
        }
      }
      

      3. Write rotateRight and test it

      Now write the method:

      1. Find YOUR TASK in the file. Delete the throw statement (both of its lines), then type your method.
      2. Tap Run. Fix and run again until you see 8 of 8 tests passed.
      3. Then change the numbers in the last line of main to one of , and compare the printed tree with your paper answer.

      The tests print a tree as text: 20B(10R,30R) means 20 is black, with 10 (red) on its left and 30 (red) on its right; a - is an empty spot. This table explains each kind of message:

      MessageWhat it means
      PASSThe tree is exactly right, and every rule and every parent link was correct after each insert.
      wrong treeThe shape or the colors are wrong. Compare the expected and got lines.
      wrong parent link at NN's parent points to the wrong node. Check every line that sets a .parent.
      NullPointerExceptionThe code followed an empty (null) link. Often root or a parent's child link was not updated.
      links form a loop or still runningSome node points back up to a node above it. Check which child (left or right) each line sets.

      Don't stop at 7 of 8

      The first seven tests use small trees. Some mistakes only show up in bigger trees, when the node that moves up has a child that changes parent. That is what the 300 random lists are for.

      No phone or data right now?

      Do step 1, write your method on paper, and trace it on this tree: rotate right at 50. Here 30 moves up, and 40 changes parent.

      2030405060

      This is good preparation, but it is only a temporary fallback: a paper trace shows you understand the steps; it does not show that your code compiles and passes the tests. Run it as soon as you can, for example in a campus computer lab. Your teacher will tell you how to submit this task.

      What this task covers, and what it doesn't

      You write one important method and test it inside a full, working insertion. You do not write the whole insertion (insert and fixUp) from scratch, and deletion is not part of this lesson.

      Part 9 · Check yourself

      Self-check

      Not graded. Tap an answer to see if you got it, and read why.

      1. What color is every newly inserted node (before any fixing)?

      2. Which of these is not a red-black tree rule?

      3. You insert a node. Its parent is red and its uncle is red. What do you do?

      4. Parent is red, uncle is black (or NIL), and the new node forms a straight line with its parent and grandparent (left-left). What is the fix?

      5. Insert 10, 30, 20 into an empty red-black tree. Which shape appears when 20 is inserted?

      6. An empty child (NIL) counts as what color?

      7. After recoloring, the root has become red. What do you do?

      8. Compared with an AVL tree, a red-black tree is:

      Score: 0 / 8

      Key takeaways

      1. A red-black tree is a BST where every node is red or black.
      2. Root is black. NIL leaves are black. No two reds in a row. Same number of blacks on every path down.
      3. New nodes are red. The only possible problem is red-red (or a red root in an empty tree).
      4. Red uncle → recolor (and check higher). Black uncle → rotate (line: 1, triangle: 2) and swap colors.
      5. The root always ends up black.
      6. In code, a rotation must also fix every parent link.
      7. Red-black trees are a little less balanced than AVL trees but rotate less often. Java's TreeMap uses one.

      You've finished the midterm topics. Next: Graphs, and how to store them, the start of the final topics.

      References

      1. T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein, Introduction to Algorithms, 4th ed. MIT Press, 2022, Chapter 13 (Red-Black Trees).
      2. L. J. Guibas and R. Sedgewick, "A dichromatic framework for balanced trees," in Proc. 19th Annual Symposium on Foundations of Computer Science, 1978, pp. 8–21.
      3. Programiz, "Red-Black Tree" and "Insertion in a Red-Black Tree." programiz.com/dsa/red-black-tree
      4. Oracle, "TreeMap (Java SE 21 & JDK 21)," Java API documentation. docs.oracle.com