J'ai essayé de résoudre le problème Défi de programmation des émetteurs radio de Hackerland .
Pour résumer, le défi est le suivant :
Hackerland est une ville unidimensionnelle avec n des maisons, où chaque maison i est situé à un certain x i sur l'axe des x. Le maire veut installer des émetteurs radio sur les toits des maisons de la ville. Chaque émetteur a une portée, k , ce qui signifie qu'il peut transmettre un signal à toutes les maisons ≤. k unités de distance.
Étant donné une carte de Hackerland et la valeur de k pouvez-vous trouver le nombre minimum d'émetteurs nécessaires pour couvrir chaque maison ?
Ma mise en œuvre est la suivante :
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);
}
}
Je suis pratiquement sûr que cette mise en œuvre est correcte. Mais veuillez voir le détail dans les fonctions : findNextTowerIndex y nextHouseNotCoveredIndex : ils parcourent le tableau un par un !
L'un de mes tests est le suivant :
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..");
}
où input.txt peut être téléchargé à partir de aquí . (Ce n'est pas le détail le plus important dans cette question, mais quand même ) Nous avons donc un tableau de 73382 maisons, et j'ai délibérément fixé la gamme d'émetteurs pour que les méthodes que j'ai bouclent beaucoup :
Voici un exemple de sortie de ce test dans ma machine :
findNextTowerIndex looped : 38213
nextHouseNotCoveredIndex looped : 13785
Took: 359 milliseconds..
J'ai aussi ce test, qui n'affirme rien, mais se contente de garder le temps :
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..");
}
où je crée au hasard 400 gammes d'émetteurs, et j'exécute le programme 400 fois J'obtiendrai les temps d'exécution suivants dans ma machine
Took: 20149 milliseconds..
Alors maintenant, j'ai dit, pourquoi ne pas utiliser la recherche binaire au lieu de parcourir le tableau ? et j'ai modifié mes implémentations comme suit :
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;
}
et je m'attends à un grand gain de performance, car maintenant je vais tout au plus boucler pour log(N) fois, au lieu de O(N) . Donc test_01 sort :
Took: 297 milliseconds..
Rappelle-toi, c'était Took : 359 millisecondes avant. Et pour le test_02 :
Took: 18047 milliseconds..
J'obtiens donc toujours des valeurs autour de 20 secondes avec l'implémentation de la marche en tableau et 18 - 19 secondes pour l'implémentation de la recherche binaire.
Je m'attendais à un gain de performance bien meilleur en utilisant Arrays.binarySearch mais ce n'est manifestement pas le cas, pourquoi ? Qu'est-ce qui me manque ? Ai-je besoin d'un tableau contenant plus de 73382 pour voir l'avantage, ou est-ce sans importance ?
Edit #01
Après le commentaire de @huck_cussler, j'ai essayé de doubler et tripler l'ensemble de données que j'ai (avec des nombres aléatoires) et j'ai essayé d'exécuter test02 (bien sûr en triplant la taille des tableaux dans le test lui-même ). Pour l'implémentation linéaire, les temps sont les suivants :
Took: 18789 milliseconds..
Took: 34396 milliseconds..
Took: 53504 milliseconds..
Pour la mise en œuvre de la recherche binaire, j'ai obtenu les valeurs suivantes :
Took: 18644 milliseconds..
Took: 33831 milliseconds..
Took: 52886 milliseconds..