An interval is a data structure that represents a range (start & end, from & to, or min & max, etc.). An Interval Tree stores these intervals in a sorted tree structure that makes searching for range intersections much faster. Interval trees help answer questions like: "find all stored intervals that at least partially overlap with the range a
to b
"
Typical interval trees store the intervals using the start of the range as the key to a binary search tree.
This code implements the interval tree and has two methods:
add(start, end)
- inserts an interval to the treeoverlap(start, end)
- identifies if any interval in the tree overlaps with the input values.
The implemented algorithm follows an Augmented
Interval Tree approach where each node maintains the maximum value contained in any of its child nodes. This maximum value is kept in sync by the add(start,end)
method. Other details of the algorithm are:
intervals are added to a binary tree with the start of interval as index to sort on.
when intervals are added the maximum values of all parent nodes are updated to ensure they are in sync.
to check for overlap it performs a descending scan of the tree using iteration, not recursion. It checks each node it descends to, and if the node:
- intersects the input arguments, it returns true.
if (leftsubtree != null && leftsubtree.max > low)
search left- else search right
Note, ranges only overlap if the ranges do more than just touch. The range
[10,20]
does not overlap with the range[20,30]
.
Despite of a brief stint in explaining my problem, more details can be obtained on this link here.
I'm looking for code review, best practices, optimizations etc. Also verifying complexity to be O(log(n)) to add
and O(log(n)) to look for overlap.
Do correct me if wrong.
public class IntervalSearchTree {
private IntervalNode root;
private class IntervalNode {
IntervalNode left;
int start;
int end;
int maxEnd;
IntervalNode right;
public IntervalNode(IntervalNode left, int start, int end, int maxEnd, IntervalNode right) {
this.left = left;
this.start = start;
this.end = end;
this.maxEnd = maxEnd;
this.right = right;
}
}
/**
* Adds an interval to the the calendar
*
* @param start the start of interval
* @param end the end of the interval.
*/
public void add (int start, int end) {
if (start >= end) throw new IllegalArgumentException("The end " + end + " should be greater than start " + start);
IntervalNode inode = root;
while (inode != null) {
inode.maxEnd = (end > inode.maxEnd) ? end : inode.maxEnd;
if (start < inode.start) {
if (inode.left == null) {
inode.left = new IntervalNode(null, start, end, end, null);
return;
}
inode = inode.left;
} else {
if (inode.right == null) {
inode.right = new IntervalNode(null, start, end, end, null);
return;
}
inode = inode.right;
}
}
root = new IntervalNode(null, start, end, end, null);
}
/**
* Tests if the input interval overlaps with the existing intervals.
*
* Rules:
* 1. If interval intersects return true. obvious.
* 2. if (leftsubtree == null || leftsubtree.max <= low) go right
* 3. else go left
*
* @param start the start of the interval
* @param end the end of the interval
* return true if overlap, else false.
*/
public boolean overlap(int start, int end) {
if (start >= end) throw new IllegalArgumentException("The end " + end + " should be greater than start " + start);
IntervalNode intervalNode = root;
while (intervalNode != null) {
if (intersection(start, end, intervalNode.start, intervalNode.end)) return true;
if (goLeft(start, end, intervalNode.left)) {
intervalNode = intervalNode.left;
} else {
intervalNode = intervalNode.right;
}
}
return false;
}
/**
* Returns if there is an intersection in the two intervals
* Two intervals such that one of the points coincide:
* eg: [10, 20] and [20, 40] are NOT considered as intersecting.
*/
private boolean intersection (int start, int end, int intervalStart, int intervalEnd) {
return start < intervalEnd && end > intervalStart;
}
private boolean goLeft(int start, int end, IntervalNode intervalLeftSubtree) {
return intervalLeftSubtree != null && intervalLeftSubtree.maxEnd > start;
}
public static void main(String[] args) {
IntervalSearchTree intervalSearchTree = new IntervalSearchTree();
intervalSearchTree.add(17, 19);
intervalSearchTree.add(5, 8);
intervalSearchTree.add(21, 24);
intervalSearchTree.add(5, 8);
intervalSearchTree.add(4, 8);
intervalSearchTree.add(15, 18);
intervalSearchTree.add(7, 10);
intervalSearchTree.add(16, 22);
System.out.println("Expected true, Actual: " + intervalSearchTree.overlap(23, 25));
System.out.println("Expected false, Actual: " + intervalSearchTree.overlap(12, 14));
System.out.println("Expected true, Actual: " + intervalSearchTree.overlap(21, 23));
// testing adjoint
System.out.println("Expected false, Actual: " + intervalSearchTree.overlap(10, 15));
System.out.println("Expected false, Actual: " + intervalSearchTree.overlap(10, 14));
System.out.println("Expected false, Actual: " + intervalSearchTree.overlap(11, 15));
}
}
[10,20]
does in fact intersect with[20,30]
. What you probably mean is that the range(10,20]
does not intersect with(20,30]
, which is true. In range notations, the square bracket means the range includes the value, and the parenthesis means it does not include the value. \$\endgroup\$