Я попытался решить проблему Hackerland Radio Transmitters.
Подводя итог, задача заключается в следующем:
Hackerland - это одномерный город с n домами, где каждый дом я расположен на некоторой оси x i по оси x. Мэр хочет установить радиопередатчики на крышах городских домов. Каждый передатчик имеет диапазон, k, что означает, что он может передавать сигнал всем домам ≤ k единиц расстояния.
Учитывая карту Hackerland и значение k, вы можете найти минимальное количество передатчиков, необходимых для покрытия каждого дома?
Моя реализация такова:
package biz.tugay;
import java.util.*;
public class HackerlandRadioTransmitters {
public static int minNumOfTransmitters(int[] houseLocations, int transmitterRange) {
// Sort and remove duplicates..
houseLocations = uniqueHouseLocationsSorted(houseLocations);
int towerCount = 0;
for (int nextHouseNotCovered = 0; nextHouseNotCovered < houseLocations.length; ) {
final int towerLocation = HackerlandRadioTransmitters.findNextTowerIndex(houseLocations, nextHouseNotCovered, transmitterRange);
towerCount++;
nextHouseNotCovered = HackerlandRadioTransmitters.nextHouseNotCoveredIndex(houseLocations, towerLocation, transmitterRange);
if (nextHouseNotCovered == -1) {
break;
}
}
return towerCount;
}
public static int findNextTowerIndex(final int[] houseLocations, final int houseNotCoveredIndex, final int transmitterRange) {
final int houseLocationWeWantToCover = houseLocations[houseNotCoveredIndex];
final int farthestHouseLocationAllowed = houseLocationWeWantToCover + transmitterRange;
int towerIndex = houseNotCoveredIndex;
int loop = 0;
while (true) {
loop++;
if (towerIndex == houseLocations.length - 1) {
break;
}
if (farthestHouseLocationAllowed >= houseLocations[towerIndex + 1]) {
towerIndex++;
continue;
}
break;
}
System.out.println("findNextTowerIndex looped : " + loop);
return towerIndex;
}
public static int nextHouseNotCoveredIndex(final int[] houseLocations, final int towerIndex, final int transmitterRange) {
final int towerCoversUntil = houseLocations[towerIndex] + transmitterRange;
int notCoveredHouseIndex = towerIndex + 1;
int loop = 0;
while (notCoveredHouseIndex < houseLocations.length) {
loop++;
final int locationOfHouseBeingChecked = houseLocations[notCoveredHouseIndex];
if (locationOfHouseBeingChecked > towerCoversUntil) {
break; // Tower does not cover the house anymore, break the loop..
}
notCoveredHouseIndex++;
}
if (notCoveredHouseIndex == houseLocations.length) {
notCoveredHouseIndex = -1;
}
System.out.println("nextHouseNotCoveredIndex looped : " + loop);
return notCoveredHouseIndex;
}
public static int[] uniqueHouseLocationsSorted(final int[] houseLocations) {
Arrays.sort(houseLocations);
final HashSet<Integer> integers = new HashSet<>();
final int[] houseLocationsUnique = new int[houseLocations.length];
int innerCounter = 0;
for (int houseLocation : houseLocations) {
if (integers.contains(houseLocation)) {
continue;
}
houseLocationsUnique[innerCounter] = houseLocation;
integers.add(houseLocationsUnique[innerCounter]);
innerCounter++;
}
return Arrays.copyOf(houseLocationsUnique, innerCounter);
}
}
Я уверен, что эта реализация верна. Но, пожалуйста, смотрите подробности в функциях: findNextTowerIndex и nextHouseNotCoveredIndex: они перемещаются по массиву один за другим!
Один из моих тестов выглядит следующим образом:
static void test_01() throws FileNotFoundException {
final long start = System.currentTimeMillis();
final File file = new File("input.txt");
final Scanner scanner = new Scanner(file);
int[] houseLocations = new int[73382];
for (int counter = 0; counter < 73382; counter++) {
houseLocations[counter] = scanner.nextInt();
}
final int[] uniqueHouseLocationsSorted = HackerlandRadioTransmitters.uniqueHouseLocationsSorted(houseLocations);
final int minNumOfTransmitters = HackerlandRadioTransmitters.minNumOfTransmitters(uniqueHouseLocationsSorted, 73381);
assert minNumOfTransmitters == 1;
final long end = System.currentTimeMillis();
System.out.println("Took: " + (end - start) + " milliseconds..");
}
где input.txt можно загрузить из здесь. (Это не самая важная деталь в этом вопросе, но все же..) Итак, у нас есть массив из 73382 домов, и я намеренно установил диапазон передатчика, чтобы методы, которые у меня были много:
Вот пример вывода этого теста на моем компьютере:
findNextTowerIndex looped : 38213
nextHouseNotCoveredIndex looped : 13785
Took: 359 milliseconds..
У меня также есть этот тест, который ничего не утверждает, а просто держит время:
static void test_02() throws FileNotFoundException {
final long start = System.currentTimeMillis();
for (int i = 0; i < 400; i ++) {
final File file = new File("input.txt");
final Scanner scanner = new Scanner(file);
int[] houseLocations = new int[73382];
for (int counter = 0; counter < 73382; counter++) {
houseLocations[counter] = scanner.nextInt();
}
final int[] uniqueHouseLocationsSorted = HackerlandRadioTransmitters.uniqueHouseLocationsSorted(houseLocations);
final int transmitterRange = ThreadLocalRandom.current().nextInt(1, 70000);
final int minNumOfTransmitters = HackerlandRadioTransmitters.minNumOfTransmitters(uniqueHouseLocationsSorted, transmitterRange);
}
final long end = System.currentTimeMillis();
System.out.println("Took: " + (end - start) + " milliseconds..");
}
где я произвольно создаю 400 диапазонов передатчиков и запускаю программу 400 раз.. Я получу время выполнения на моей машине.
Took: 20149 milliseconds..
Итак, теперь я сказал: почему я не использую двоичный поиск вместо того, чтобы ходить по массиву и менял свои реализации следующим образом:
public static int findNextTowerIndex(final int[] houseLocations, final int houseNotCoveredIndex, final int transmitterRange) {
final int houseLocationWeWantToCover = houseLocations[houseNotCoveredIndex];
final int farthestHouseLocationAllowed = houseLocationWeWantToCover + transmitterRange;
int nextTowerIndex = Arrays.binarySearch(houseLocations, 0, houseLocations.length, farthestHouseLocationAllowed);
if (nextTowerIndex < 0) {
nextTowerIndex = -nextTowerIndex;
nextTowerIndex = nextTowerIndex -2;
}
return nextTowerIndex;
}
public static int nextHouseNotCoveredIndex(final int[] houseLocations, final int towerIndex, final int transmitterRange) {
final int towerCoversUntil = houseLocations[towerIndex] + transmitterRange;
int nextHouseNotCoveredIndex = Arrays.binarySearch(houseLocations, 0, houseLocations.length, towerCoversUntil);
if (-nextHouseNotCoveredIndex > houseLocations.length) {
return -1;
}
if (nextHouseNotCoveredIndex < 0) {
nextHouseNotCoveredIndex = - (nextHouseNotCoveredIndex + 1);
return nextHouseNotCoveredIndex;
}
return nextHouseNotCoveredIndex + 1;
}
и я ожидаю большого повышения производительности, так как теперь я буду в большинстве циклов для log (N) раз, а не O (N). Итак, выходы test_01:
Took: 297 milliseconds..
Помните, что это было: 359 миллисекунд.. раньше. И для test_02:
Took: 18047 milliseconds..
Поэтому я всегда получаю значения около 20 секунд с реализацией реализации шага и 18-19 секунд для реализации бинарного поиска.
Я ожидал гораздо лучшего прироста производительности, используя Arrays.binarySearch, но, очевидно, это не так, почему это так? Что мне не хватает? Нужен ли мне массив с более чем 73382, чтобы увидеть преимущество, или это не имеет значения?
Изменить # 01
После комментария @huck_cussler я попытался удвоить и утроить набор данных, который у меня есть (со случайными числами), и попытался запустить test02 (конечно, с утроением размеров массива в самом тесте..). Для линейной реализации время имеет следующий вид:
Took: 18789 milliseconds..
Took: 34396 milliseconds..
Took: 53504 milliseconds..
Для реализации бинарного поиска я получил следующие значения:
Took: 18644 milliseconds..
Took: 33831 milliseconds..
Took: 52886 milliseconds..