Implement a binary tree (#5431)
* Add code for the article 'Java Primitives versus Objects' * Use JMH for benchmarking the primitive and wrapper classes * Uncomment the benchmarks and remove unused class * Add a binary search tree implementation * Add an example of how to use the binary tree class * Adjust the print statements
This commit is contained in:
parent
f824da90ba
commit
44a3d36bd9
@ -0,0 +1,19 @@
|
||||
package com.baeldung.datastructures
|
||||
|
||||
/**
|
||||
* Example of how to use the {@link Node} class.
|
||||
*
|
||||
*/
|
||||
fun main(args: Array<String>) {
|
||||
val tree = Node(4)
|
||||
val keys = arrayOf(8, 15, 21, 3, 7, 2, 5, 10, 2, 3, 4, 6, 11)
|
||||
for (key in keys) {
|
||||
tree.insert(key)
|
||||
}
|
||||
val node = tree.find(4)!!
|
||||
println("Node with value ${node.key} [left = ${node.left?.key}, right = ${node.right?.key}]")
|
||||
println("Delete node with key = 3")
|
||||
node.delete(3)
|
||||
print("Tree content after the node elimination: ")
|
||||
println(tree.visit().joinToString { it.toString() })
|
||||
}
|
167
core-kotlin/src/main/kotlin/com/baeldung/datastructures/Node.kt
Normal file
167
core-kotlin/src/main/kotlin/com/baeldung/datastructures/Node.kt
Normal file
@ -0,0 +1,167 @@
|
||||
package com.baeldung.datastructures
|
||||
|
||||
/**
|
||||
* An ADT for a binary search tree.
|
||||
* Note that this data type is neither immutable nor thread safe.
|
||||
*/
|
||||
class Node(
|
||||
var key: Int,
|
||||
var left: Node? = null,
|
||||
var right: Node? = null) {
|
||||
|
||||
/**
|
||||
* Return a node with given value. If no such node exists, return null.
|
||||
* @param value
|
||||
*/
|
||||
fun find(value: Int): Node? = when {
|
||||
this.key > value -> left?.find(value)
|
||||
this.key < value -> right?.find(value)
|
||||
else -> this
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a given value into the tree.
|
||||
* After insertion, the tree should contain a node with the given value.
|
||||
* If the tree already contains the given value, nothing is performed.
|
||||
* @param value
|
||||
*/
|
||||
fun insert(value: Int) {
|
||||
if (value > this.key) {
|
||||
if (this.right == null) {
|
||||
this.right = Node(value)
|
||||
} else {
|
||||
this.right?.insert(value)
|
||||
}
|
||||
} else if (value < this.key) {
|
||||
if (this.left == null) {
|
||||
this.left = Node(value)
|
||||
} else {
|
||||
this.left?.insert(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the value from the given tree. If the tree does not contain the value, the tree remains unchanged.
|
||||
* @param value
|
||||
*/
|
||||
fun delete(value: Int) {
|
||||
when {
|
||||
value > key -> scan(value, this.right, this)
|
||||
value < key -> scan(value, this.left, this)
|
||||
else -> removeNode(this, null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the tree in the search of the given value.
|
||||
* @param value
|
||||
* @param node sub-tree that potentially might contain the sought value
|
||||
* @param parent node's parent
|
||||
*/
|
||||
private fun scan(value: Int, node: Node?, parent: Node?) {
|
||||
if (node == null) {
|
||||
System.out.println("value " + value
|
||||
+ " seems not present in the tree.")
|
||||
return
|
||||
}
|
||||
when {
|
||||
value > node.key -> scan(value, node.right, node)
|
||||
value < node.key -> scan(value, node.left, node)
|
||||
value == node.key -> removeNode(node, parent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the node.
|
||||
*
|
||||
* Removal process depends on how many children the node has.
|
||||
*
|
||||
* @param node node that is to be removed
|
||||
* @param parent parent of the node to be removed
|
||||
*/
|
||||
private fun removeNode(node: Node, parent: Node?) {
|
||||
node.left?.let { leftChild ->
|
||||
run {
|
||||
node.right?.let {
|
||||
removeTwoChildNode(node)
|
||||
} ?: removeSingleChildNode(node, leftChild)
|
||||
}
|
||||
} ?: run {
|
||||
node.right?.let { rightChild -> removeSingleChildNode(node, rightChild) } ?: removeNoChildNode(node, parent)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the node without children.
|
||||
* @param node
|
||||
* @param parent
|
||||
*/
|
||||
private fun removeNoChildNode(node: Node, parent: Node?) {
|
||||
parent?.let { p ->
|
||||
if (node == p.left) {
|
||||
p.left = null
|
||||
} else if (node == p.right) {
|
||||
p.right = null
|
||||
}
|
||||
} ?: throw IllegalStateException(
|
||||
"Can not remove the root node without child nodes")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node that has two children.
|
||||
*
|
||||
* The process of elimination is to find the biggest key in the left sub-tree and replace the key of the
|
||||
* node that is to be deleted with that key.
|
||||
*/
|
||||
private fun removeTwoChildNode(node: Node) {
|
||||
val leftChild = node.left!!
|
||||
leftChild.right?.let {
|
||||
val maxParent = findParentOfMaxChild(leftChild)
|
||||
maxParent.right?.let {
|
||||
node.key = it.key
|
||||
maxParent.right = null
|
||||
} ?: throw IllegalStateException("Node with max child must have the right child!")
|
||||
|
||||
} ?: run {
|
||||
node.key = leftChild.key
|
||||
node.left = leftChild.left
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a node whose right child contains the biggest value in the given sub-tree.
|
||||
* Assume that the node n has a non-null right child.
|
||||
*
|
||||
* @param n
|
||||
*/
|
||||
private fun findParentOfMaxChild(n: Node): Node {
|
||||
return n.right?.let { r -> r.right?.let { findParentOfMaxChild(r) } ?: n }
|
||||
?: throw IllegalArgumentException("Right child must be non-null")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a parent that has only one child.
|
||||
* Removal is effectively is just coping the data from the child parent to the parent parent.
|
||||
* @param parent Node to be deleted. Assume that it has just one child
|
||||
* @param child Assume it is a child of the parent
|
||||
*/
|
||||
private fun removeSingleChildNode(parent: Node, child: Node) {
|
||||
parent.key = child.key
|
||||
parent.left = child.left
|
||||
parent.right = child.right
|
||||
}
|
||||
|
||||
fun visit(): Array<Int> {
|
||||
val a = left?.visit() ?: emptyArray()
|
||||
val b = right?.visit() ?: emptyArray()
|
||||
return a + arrayOf(key) + b
|
||||
}
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
package com.baeldung.datastructures
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class NodeTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test suit for finding the node by value
|
||||
* Partition the tests as follows:
|
||||
* 1. tree depth: 0, 1, > 1
|
||||
* 2. pivot depth location: not present, 0, 1, 2, > 2
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find the node by value
|
||||
* 1. tree depth: 0
|
||||
* 2. pivot depth location: not present
|
||||
*/
|
||||
@Test
|
||||
fun givenDepthZero_whenPivotNotPresent_thenNull() {
|
||||
val n = Node(1)
|
||||
assertNull(n.find(2))
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the node by value
|
||||
* 1. tree depth: 0
|
||||
* 2. pivot depth location: 0
|
||||
*/
|
||||
@Test
|
||||
fun givenDepthZero_whenPivotDepthZero_thenReturnNodeItself() {
|
||||
val n = Node(1)
|
||||
assertEquals(n, n.find(1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the node by value
|
||||
* 1. tree depth: 1
|
||||
* 2. pivot depth location: not present
|
||||
*/
|
||||
@Test
|
||||
fun givenDepthOne_whenPivotNotPresent_thenNull() {
|
||||
val n = Node(1, Node(0))
|
||||
assertNull(n.find(2))
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the node by value
|
||||
* 1. tree depth: 1
|
||||
* 2. pivot depth location: not present
|
||||
*/
|
||||
@Test
|
||||
fun givenDepthOne_whenPivotAtDepthOne_thenSuccess() {
|
||||
val n = Node(1, Node(0))
|
||||
assertEquals(n.left, n.find(0)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenDepthTwo_whenPivotAtDepth2_then_Success() {
|
||||
val left = Node(1, Node(0), Node(2))
|
||||
val right = Node(5, Node(4), Node(6))
|
||||
val n = Node(3, left, right)
|
||||
assertEquals(left.left, n.find(0))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test suit for inserting a value
|
||||
* Partition the test as follows:
|
||||
* 1. tree depth: 0, 1, 2, > 2
|
||||
* 2. depth to insert: 0, 1, > 1
|
||||
* 3. is duplicate: no, yes
|
||||
* 4. sub-tree: left, right
|
||||
*/
|
||||
/**
|
||||
* Test for inserting a value
|
||||
* 1. tree depth: 0
|
||||
* 2. depth to insert: 1
|
||||
* 3. is duplicate: no
|
||||
* 4. sub-tree: right
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthZero_whenInsertNoDuplicateToRight_thenAddNode() {
|
||||
val n = Node(1)
|
||||
n.insert(2)
|
||||
assertEquals(1, n.key)
|
||||
with(n.right!!) {
|
||||
assertEquals(2, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
assertNull(n.left)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for inserting a value
|
||||
* 1. tree depth: 0
|
||||
* 2. depth to insert: 1
|
||||
* 3. is duplicate: no
|
||||
* 4. sub-tree: right
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthZero_whenInsertNoDuplicateToLeft_thenSuccess() {
|
||||
val n = Node(1)
|
||||
n.insert(0)
|
||||
assertEquals(1, n.key)
|
||||
with(n.left!!) {
|
||||
assertEquals(0, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
assertNull(n.right)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for inserting a value
|
||||
* 1. tree depth: 0
|
||||
* 2. depth to insert: 1
|
||||
* 3. is duplicate: yes
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthZero_whenInsertDuplicate_thenSuccess() {
|
||||
val n = Node(1)
|
||||
n.insert(1)
|
||||
assertEquals(1, n.key)
|
||||
assertNull(n.right)
|
||||
assertNull(n.left)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test suit for inserting a value
|
||||
* Partition the test as follows:
|
||||
* 1. tree depth: 0, 1, 2, > 2
|
||||
* 2. depth to insert: 0, 1, > 1
|
||||
* 3. is duplicate: no, yes
|
||||
* 4. sub-tree: left, right
|
||||
*/
|
||||
/**
|
||||
* Test for inserting a value
|
||||
* 1. tree depth: 1
|
||||
* 2. depth to insert: 1
|
||||
* 3. is duplicate: no
|
||||
* 4. sub-tree: right
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthOne_whenInsertNoDuplicateToRight_thenSuccess() {
|
||||
val n = Node(10, Node(3))
|
||||
n.insert(15)
|
||||
assertEquals(10, n.key)
|
||||
with(n.right!!) {
|
||||
assertEquals(15, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
with(n.left!!) {
|
||||
assertEquals(3, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for inserting a value
|
||||
* 1. tree depth: 1
|
||||
* 2. depth to insert: 1
|
||||
* 3. is duplicate: no
|
||||
* 4. sub-tree: left
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthOne_whenInsertNoDuplicateToLeft_thenAddNode() {
|
||||
val n = Node(10, null, Node(15))
|
||||
n.insert(3)
|
||||
assertEquals(10, n.key)
|
||||
with(n.right!!) {
|
||||
assertEquals(15, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
with(n.left!!) {
|
||||
assertEquals(3, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for inserting a value
|
||||
* 1. tree depth: 1
|
||||
* 2. depth to insert: 1
|
||||
* 3. is duplicate: yes
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthOne_whenInsertDuplicate_thenNoChange() {
|
||||
val n = Node(10, null, Node(15))
|
||||
n.insert(15)
|
||||
assertEquals(10, n.key)
|
||||
with(n.right!!) {
|
||||
assertEquals(15, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
assertNull(n.left)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test suit for removal
|
||||
* Partition the input as follows:
|
||||
* 1. tree depth: 0, 1, 2, > 2
|
||||
* 2. value to delete: absent, present
|
||||
* 3. # child nodes: 0, 1, 2
|
||||
*/
|
||||
/**
|
||||
* Test for removal value
|
||||
* 1. tree depth: 0
|
||||
* 2. value to delete: absent
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthZero_whenValueAbsent_thenNoChange() {
|
||||
val n = Node(1)
|
||||
n.delete(0)
|
||||
assertEquals(1, n.key)
|
||||
assertNull(n.left)
|
||||
assertNull(n.right)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for removal
|
||||
* 1. tree depth: 1
|
||||
* 2. value to delete: absent
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthOne_whenValueAbsent_thenNoChange() {
|
||||
val n = Node(1, Node(0), Node(2))
|
||||
n.delete(3)
|
||||
assertEquals(1, n.key)
|
||||
assertEquals(2, n.right!!.key)
|
||||
with(n.left!!) {
|
||||
assertEquals(0, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
with(n.right!!) {
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test suit for removal
|
||||
* 1. tree depth: 1
|
||||
* 2. value to delete: present
|
||||
* 3. # child nodes: 0
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthOne_whenNodeToDeleteHasNoChildren_thenChangeTree() {
|
||||
val n = Node(1, Node(0))
|
||||
n.delete(0)
|
||||
assertEquals(1, n.key)
|
||||
assertNull(n.left)
|
||||
assertNull(n.right)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test suit for removal
|
||||
* 1. tree depth: 2
|
||||
* 2. value to delete: present
|
||||
* 3. # child nodes: 1
|
||||
*/
|
||||
@Test
|
||||
fun givenTreeDepthTwo_whenNodeToDeleteHasOneChild_thenChangeTree() {
|
||||
val n = Node(2, Node(0, null, Node(1)), Node(3))
|
||||
n.delete(0)
|
||||
assertEquals(2, n.key)
|
||||
with(n.right!!) {
|
||||
assertEquals(3, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
with(n.left!!) {
|
||||
assertEquals(1, key)
|
||||
assertNull(left)
|
||||
assertNull(right)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTreeDepthThree_whenNodeToDeleteHasTwoChildren_thenChangeTree() {
|
||||
val l = Node(2, Node(1), Node(5, Node(4), Node(6)))
|
||||
val r = Node(10, Node(9), Node(11))
|
||||
val n = Node(8, l, r)
|
||||
n.delete(8)
|
||||
assertEquals(6, n.key)
|
||||
with(n.left!!) {
|
||||
assertEquals(2, key)
|
||||
assertEquals(1, left!!.key)
|
||||
assertEquals(5, right!!.key)
|
||||
assertEquals(4, right!!.left!!.key)
|
||||
}
|
||||
with(n.right!!) {
|
||||
assertEquals(10, key)
|
||||
assertEquals(9, left!!.key)
|
||||
assertEquals(11, right!!.key)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user