Первая попытка построить дерево из диапазонов.
Сначала вы найдете корень поддерева, в котором содержатся все решения запроса. Это довольно легко. Однако: это поддерево содержит некоторые значения вне набора решений.
Таким образом, чтобы найти наименьшее число, вы должны дважды пройти по дереву (от корня вниз по левой стороне дерева и снова от корня вниз по правой стороне). В каждой точке этих обходов вы будете проверять, нашли ли вы решение ниже, чем ваше предыдущее решение.
Когда это будет сделано, самое низкое решение в этом наборе будет самым низким.
Поскольку проверка решений каждого узла требовала бинарного поиска по списку индексов в этом поддереве, в конечном итоге вы работаете с O (ln (n) ^ 2), что слишком медленно.
Улучшение этого связано с тем, что это было бы легко, если бы мы всегда искали первый элемент в массиве. Тогда вам не нужно выполнять бинарный поиск, вы просто получаете первый элемент.
Чтобы добраться до этого места, вы должны построить n деревьев.
tree[0] has all values in the initializaing array
tree[1] has all but the first.
tree[2] has all but the first and second.
Таким образом, каждое из этих деревьев содержит единственное наилучшее решение.
Таким образом, поиск выполняется в O (ln (n)).
В примере кода мы позволили себе построить дерево, не реализовав собственные структуры данных.
Вы создаете массив узлов, делаете копию этого массива и сортируете одну из копий. Это работает в O (n ln (n)). Другая копия используется для поиска позиции в отсортированном массиве, поэтому вы можете удалять элемент каждый раз, когда строите другое дерево.
Конечно, можно удалить узел в связанном списке за постоянное время, но, поскольку у вас есть только ссылка на объект, я подозреваю, что реализация java должна искать в связанном списке элемент, подлежащий удалению, поэтому построение деревьев, вероятно, занимает O (n ^ 2) в этом примере. Легко исправить, хотя.
public class ArraySearcher {
public class SegmentNode implements Comparable<SegmentNode> {
int min;
int max;
SegmentNode root;
SegmentNode left;
SegmentNode right;
int lowIndex;
public int compareTo(final SegmentNode obj)
{
return this.min - obj.min;
}
public boolean contains(final int n)
{
if ((n > this.min) && (n < this.max)) {
return true;
} else {
return false;
}
}
/**
* Given the root of a tree, this method will find the center node. The
* center node is defined as the first node in the tree who's left and
* right nodes either contain solutions or are null.
*
* If no node can be found which contains solution, this will return
* null.
*
* @param a
* @param b
* @return
*/
public SegmentNode findCenter(final int a, final int b)
{
SegmentNode currentNode = this;
while (true) {
/*
* first check to see if no solution can be found in this node.
* There is no solution if both nodes are
*/
if ((a < currentNode.min && b < currentNode.min)
|| (a > currentNode.max && b > currentNode.max)) {
return null;
}
/*
* Now check to see if this is the center.
*/
if (((currentNode.left == null) || (a < currentNode.left.max))
&& ((currentNode.right == null) || (b > currentNode.right.min))) {
// this is the center, return it.
return currentNode;
} else if ((currentNode.left == null)
|| (a < currentNode.left.max)) {
// no solution on one side, is it in the left?
currentNode = currentNode.left;
} else {
currentNode = currentNode.right;
}
}
}
public SegmentNode findLeft(final int seekMin)
{
/*
* keep going left until
*/
if ((this.min > seekMin)) {
return this;
} else {
if (this.right == null) {
return null;
}
return this.right.findLeft(seekMin);
}
}
/**
* This method can be called on the center node. it traverses left
*
* @param a
* @param b
* @return
*/
public Integer findLeftmostLowestIndex(final int n)
{
if (null == this.left) {
return null;
}
int lowest = Integer.MAX_VALUE;
SegmentNode current = this.left;
// update the lowest with the right node if it is greater
// than the current node.
while (true) {
// collect the best value from the right and/or left
// if they are greater than N
if ((null != current.left) && (current.left.min > n)) {
lowest = current.left.lowIndex(lowest);
}
if ((null != current.right) && (current.right.min > n)) {
lowest = current.right.lowIndex(lowest);
}
if ((null == current.left) && (null == current.right)
&& (current.max > n)) {
lowest = current.lowIndex(lowest);
}
// quit when we've gone as far left as we can
int seek = current.leftSeek(n);
if (seek == 0) {
break;
} else if (seek < 0) {
current = current.left;
} else {
current = current.right;
}
}
if (lowest == Integer.MAX_VALUE) {
return null;
} else {
return new Integer(lowest);
}
}
public SegmentNode findMatch(final int seekMin, final int seekMax)
{
if ((this.min > seekMin) && (this.max < seekMax)) {
return this;
} else if ((this.min > seekMin) && (this.left != null)) {
return this.left.findMatch(seekMin, seekMax);
} else {
if (this.right == null) {
return null;
}
return this.right.findMatch(seekMin, seekMax);
}
}
public SegmentNode findMatchRight(final int seekMin, final int seekMax)
{
if ((this.min > seekMin) && (this.max < seekMax)) {
return this;
} else if (this.max < seekMax) {
if (this.right == null) {
return null;
}
return this.right.findMatchRight(seekMin, seekMax);
} else {
if (this.left == null) {
return null;
}
return this.left.findMatchRight(seekMin, seekMax);
}
}
/**
* Search for the first number in the tree which is lower than n.
*
* @param n
* @return
*/
public Integer findRightmostLowestIndex(final int n)
{
if (null == this.left) {
return null;
}
int lowest = Integer.MAX_VALUE;
SegmentNode current = this.right;
// update the lowest with the right node if it is greater
// than the current node.
while (true) {
// collect the best value from the right and/or left //this.max
// < b
// if they are greater than N
if ((null != current.left) && (current.left.max < n)) {
lowest = current.left.lowIndex(lowest);
}
if ((null != current.right) && (current.right.max < n)) {
lowest = current.right.lowIndex(lowest);
}
if ((null == current.left) && (null == current.right)
&& (current.min < n)) {
lowest = current.lowIndex(lowest);
}
// quit when we've gone as far left as we can
int seek = current.rightSeek(n);
if (seek == 0) {
break;
} else if (seek < 0) {
current = current.left;
} else {
current = current.right;
}
}
if (lowest == Integer.MAX_VALUE) {
return null;
} else {
return new Integer(lowest);
}
}
/**
*
* @param seekMin
* @param seekMax
* @return
*/
public SegmentNode findSegmentRoot(final int seekMin, final int seekMax)
{
return null;
}
public int leftSeek(final int n)
{
if ((null == this.left) && (null == this.right)) {
return 0;
// } else if ((null != this.left) && (this.left.max > n)) {
} else if ((null != this.left) && ((n < this.left.max))
|| (this.left.contains(n))) {
return -1;
// } else if ((null != this.right) && (this.right.min <= n)) {
} else if ((null != this.right) && ((n >= this.right.min))) {
return +1;
} else {
return 0;
}
}
public int rightSeek(final int n)
{
if ((null == this.left) && (null == this.right)) {
return 0;
} else if ((null != this.left) && (this.left.max >= n)) {
return -1;
} else if ((null != this.right) && (this.right.min < n)) {
return +1;
} else {
return 0;
}
}
@Override
public String toString()
{
StringBuilder value = new StringBuilder();
if (null != this.left) {
value.append("{ { " + this.left.min + ", " + this.left.max
+ "} }, ");
} else {
value.append("{ " + this.min + " }, ");
}
if (null != this.right) {
value.append("{ { " + this.right.min + ", " + this.right.max
+ "} }");
} else {
value.append("{ " + this.max + " }, ");
}
return value.toString();
}
private int lowIndex(final int lowest)
{
if (lowest < this.lowIndex) {
return lowest;
} else {
return this.lowIndex;
}
}
}
public static int bruteForceSearch(final int[] array, final int a,
final int b, final int c)
{
// search from c onward
/**
* search for the first value of the array that falls between a and b
* ignore everything before index of c
*/
for (int i = c; i < array.length; i++) {
if ((a < array[i]) && (array[i] < b)) {
return i;
}
}
return -1;
}
SegmentNode[] trees;
public ArraySearcher(final int[] array)
{
buildTree(array);
}
public void buildTree(final int[] array)
{
ArrayList<SegmentNode> mNodes = new ArrayList<SegmentNode>();
for (int i = 0; i < array.length; i++) {
SegmentNode mCurrentNode = new SegmentNode();
mCurrentNode.lowIndex = i;
mCurrentNode.min = array[i];
mCurrentNode.max = array[i];
mNodes.add(mCurrentNode);
}
ArrayList<SegmentNode> unsortedClone =
new ArrayList<SegmentNode>(mNodes);
// n (ln (n) )
Collections.sort(mNodes);
LinkedList<SegmentNode> nodesList = new LinkedList<SegmentNode>(mNodes);
this.trees = new SegmentNode[nodesList.size()];
for (int i = 0; i < this.trees.length; i++) {
this.trees[i] = merge(nodesList, 0, nodesList.size() - 1);
// we remove the ith one at each iteration
nodesList.remove(unsortedClone.get(i));
}
}
/**
*
* @param nodes
* @param i
* @param j
* @return
*/
public SegmentNode merge(final List<SegmentNode> nodes, final int i,
final int j)
{
if (i > j) {
throw new AssertionError();
}
SegmentNode left;
SegmentNode right;
int count = j - i;
if (count == 1) {
SegmentNode mParent = merge(nodes.get(i), nodes.get(i + 1));
return mParent;
} else if (count == 0) {
return nodes.get(i);
} else {
int mid = (count / 2) + i;
left = merge(nodes, i, mid);
right = merge(nodes, mid + 1, j);
}
return merge(left, right);
}
/**
* Build a parent segment from two other segments.
*
* @param a
* @param b
* @return
*/
public SegmentNode merge(final SegmentNode a, final SegmentNode b)
{
SegmentNode parent = new SegmentNode();
parent.root = parent;
parent.min = a.min;
parent.left = a;
parent.max = b.max;
parent.right = b;
if (a.lowIndex > b.lowIndex) {
parent.lowIndex = b.lowIndex;
b.root = parent;
} else {
parent.lowIndex = a.lowIndex;
a.root = parent;
}
return parent;
}
/**
* The work horse, find all the points with indexes greater than c that lie
* between a and b.
*
* @param a
* @param b
* @param c
* @return
*/
public Integer search(final int a, final int b, final int c)
{
if (c < this.trees.length) {
SegmentNode root = this.trees[c];
if ((a > root.max) || (b < root.min)) {
return null;
}
SegmentNode center = root.findCenter(a, b);
if (null == center) {
return null;
}
// special case to deal with a node with no children.
if ((center.left == null) && (center.right == null)) {
if ((a < center.min) && (b > center.max)) {
return new Integer(center.lowIndex);
} else {
return null;
}
}
Integer right = center.findRightmostLowestIndex(b);
Integer left = center.findLeftmostLowestIndex(a);
if ((null == right) && (null == left)) {
return null;
} else if (null == right) {
return left;
} else if (null == left) {
return right;
} else if (right.compareTo(left) > 0) {
return left;
} else {
return right;
}
} else {
return null;
}
}
}
кстати, я до сих пор использую подобные тесты, чтобы проверить его на основе подхода грубой силы:
static void testBoth(final int[] array, final ArraySearcher searcher,
final int a, final int b, final int c)
{
System.out.println("ArraySearcherTest.testBoth(array, mSearcher, " + a
+ ", " + b + ", " + c + ");");
int expected = ArraySearcher.bruteForceSearch(array, a, b, c);
Integer calcObj = searcher.search(a, b, c);
int calcInt = -1;
if (null != calcObj) {
calcInt = calcObj.intValue();
}
assertEquals(expected, calcInt);
}
@Test
public void randomizedProblemTester()
{
for (int i = 0; i < 100; i++) {
// build arrays from 5 to 20 elements long
int[] array = new int[TestUtils.randomInt(5, 20)];
System.out.print("int[] array = {");
for (int j = 0; j < array.length; j++) {
// with values from 0 to 100
array[j] = TestUtils.randomInt(0, 100);
System.out.print(array[j] + ", ");
}
System.out.println("};");
ArraySearcher mSearcher = new ArraySearcher(array);
for (int j = 0; j < array.length; j++) {
int a = TestUtils.randomInt(0, 100);
int b = TestUtils.randomInt(a, 100);
int c = TestUtils.randomInt(0, 20);
ArraySearcherTest.testBoth(array, mSearcher, a, b, c);
}
}
}