Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions src/main/java/org/nintynine/problems/BTreeP68.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package org.nintynine.problems;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
* Problem P68: Preorder and inorder sequences of binary trees.
*
* <p>See issue #61.
*/
@SuppressWarnings("checkstyle:AbbreviationAsWordInName")
public class BTreeP68 {

private BTreeP68() {
// utility class
}

/** Simple binary tree node. */
public static class Node {
char value;
Node left;
Node right;

Node(char value) {
this.value = value;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Node other)) {
return false;
}
return value == other.value
&& Objects.equals(left, other.left)
&& Objects.equals(right, other.right);
}

@Override
public int hashCode() {
return Objects.hash(value, left, right);
}

@Override
public String toString() {
if (left == null && right == null) {
return String.valueOf(value);
}
return value
+ "(" + (left == null ? "NIL" : left.toString())
+ "," + (right == null ? "NIL" : right.toString()) + ")";
}
}

/** Preorder sequence for the given tree. */
public static List<Character> preorder(Node node) {
List<Character> result = new ArrayList<>();
preorderRec(node, result);
return result;
}

private static void preorderRec(Node node, List<Character> acc) {
if (node == null) {
return;
}
acc.add(node.value);
preorderRec(node.left, acc);
preorderRec(node.right, acc);
}

/** Inorder sequence for the given tree. */
public static List<Character> inorder(Node node) {
List<Character> result = new ArrayList<>();
inorderRec(node, result);
return result;
}

private static void inorderRec(Node node, List<Character> acc) {
if (node == null) {
return;
}
inorderRec(node.left, acc);
acc.add(node.value);
inorderRec(node.right, acc);
}

/**
* Construct a binary search tree from its preorder sequence.
*
* @param seq preorder sequence
* @return root of the constructed tree
*/
public static Node fromPreorder(List<Character> seq) {
Node root = null;
if (seq == null) {
return null;
}
for (char c : seq) {
root = insert(root, c);
}
return root;
}

private static Node insert(Node node, char value) {
if (node == null) {
return new Node(value);
}
if (value < node.value) {
node.left = insert(node.left, value);
} else if (value > node.value) {
node.right = insert(node.right, value);
}
return node;
}

/**
* Reconstruct a tree from its preorder and inorder sequences.
*
* @param preorder preorder sequence
* @param inorder inorder sequence
* @return reconstructed tree root
* @throws IllegalArgumentException if inputs are invalid
*/
public static Node preInTree(List<Character> preorder, List<Character> inorder) {
if (preorder == null || inorder == null || preorder.size() != inorder.size()) {
throw new IllegalArgumentException("invalid traversals");
}
Map<Character, Integer> index = new HashMap<>();
for (int i = 0; i < inorder.size(); i++) {
index.put(inorder.get(i), i);
}
Index preIdx = new Index();
return build(preorder, 0, inorder.size() - 1, index, preIdx);
}

private static Node build(
List<Character> preorder,
int inStart,
int inEnd,
Map<Character, Integer> index,
Index preIdx) {
if (inStart > inEnd) {
return null;
}
char rootVal = preorder.get(preIdx.pos++);
Node node = new Node(rootVal);
int inIndex = index.get(rootVal);
node.left = build(preorder, inStart, inIndex - 1, index, preIdx);
node.right = build(preorder, inIndex + 1, inEnd, index, preIdx);
return node;
}

private static class Index {
int pos;
}
}
103 changes: 103 additions & 0 deletions src/test/java/org/nintynine/problems/BTreeP68Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package org.nintynine.problems;

import java.util.List;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class BTreeP68Test {

private BTreeP68.Node exampleTree() {
BTreeP68.Node a = new BTreeP68.Node('A');
BTreeP68.Node b = new BTreeP68.Node('B');
BTreeP68.Node c = new BTreeP68.Node('C');
BTreeP68.Node d = new BTreeP68.Node('D');
BTreeP68.Node e = new BTreeP68.Node('E');
BTreeP68.Node f = new BTreeP68.Node('F');
BTreeP68.Node g = new BTreeP68.Node('G');
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.right = f;
f.left = g;
return a;
}

@Test
void testPreorderAndInorder() {
BTreeP68.Node root = exampleTree();
List<Character> pre = BTreeP68.preorder(root);
List<Character> in = BTreeP68.inorder(root);
assertEquals(List.of('A','B','D','E','C','F','G'), pre);
assertEquals(List.of('D','B','E','A','C','G','F'), in);
}

@Test
void testFromPreorder() {
List<Character> seq = List.of('F','C','A','E','H','G');
BTreeP68.Node root = BTreeP68.fromPreorder(seq);
assertEquals(seq, BTreeP68.preorder(root));
List<Character> inorder = BTreeP68.inorder(root);
assertEquals(List.of('A','C','E','F','G','H'), inorder); // inorder sorted
}

@Test
void testPreInTree() {
BTreeP68.Node root = exampleTree();
List<Character> pre = BTreeP68.preorder(root);
List<Character> in = BTreeP68.inorder(root);
BTreeP68.Node rebuilt = BTreeP68.preInTree(pre, in);
assertEquals(pre, BTreeP68.preorder(rebuilt));
assertEquals(in, BTreeP68.inorder(rebuilt));
assertEquals(root, rebuilt);
}

@Test
void testPreInTreeInvalid() {
assertThrows(IllegalArgumentException.class, () ->
BTreeP68.preInTree(List.of('A'), List.of('A','B')));
}

@Test
void testNodeEqualityAndHash() {
BTreeP68.Node n1 = new BTreeP68.Node('A');
n1.left = new BTreeP68.Node('B');
BTreeP68.Node n2 = new BTreeP68.Node('A');
n2.left = new BTreeP68.Node('B');
assertEquals(n1, n2); // same structure
assertEquals(n1, n1); // self equality
assertNotEquals(n1, "other");
assertNotEquals(n1, null);
assertEquals(n1.hashCode(), n2.hashCode());

assertNotEquals(n1, new BTreeP68.Node('X'));
BTreeP68.Node n3 = new BTreeP68.Node('A');
n3.left = new BTreeP68.Node('B');
n3.right = new BTreeP68.Node('C');
assertNotEquals(n1, n3);

n2.left.value = 'C';
assertNotEquals(n1, n2);
}

@Test
void testNodeToString() {
BTreeP68.Node leaf = new BTreeP68.Node('X');
assertEquals("X", leaf.toString());

BTreeP68.Node root = new BTreeP68.Node('A');
root.left = new BTreeP68.Node('B');
root.right = new BTreeP68.Node('C');
assertEquals("A(B,C)", root.toString());

BTreeP68.Node half = new BTreeP68.Node('A');
half.left = new BTreeP68.Node('B');
assertEquals("A(B,NIL)", half.toString());

BTreeP68.Node halfRight = new BTreeP68.Node('A');
halfRight.right = new BTreeP68.Node('B');
assertEquals("A(NIL,B)", halfRight.toString());
}
}