web-dev-qa-db-fra.com

Quel est l'algorithme le plus rapide pour déterminer si un nombre dans un tableau trié est multiple de `x`?

Étant donné un entier positif x et un tableau d’entiers positifs triés A

Existe-t-il un algorithme plus rapide que O(N) pour déterminer si un élément de A est un multiple de x? Il n'y a pas d'éléments négatifs dans A.

Une boucle naïve A une fois est ma seule idée pour l’instant, je ne sais pas s’il est possible de tirer parti du fait que A est trié pour accélérer les choses.

17
shole

Cela semble dépendre beaucoup de la taille de x et du nombre d'éléments dans A, et en particulier du nombre de multiples candidats de x dans A.

La recherche binaire d'un nombre spécifique dans A prend O(log(n)) temps (n étant le nombre d'éléments dans A), donc s'il existe k possibles multiples de x entre le premier et le dernier élément de A , il faudra O(k * log(N)) pour tous les vérifier. Si ce nombre est inférieur à n, vous pouvez utiliser cet algorithme, sinon effectuez simplement une recherche linéaire.

(En outre, il existe probablement quelques petites optimisations à l’algorithme ci-dessus. Par exemple, une fois que vous avez coché x*i (et que vous ne l’avez pas trouvé), vous pouvez utiliser la position où x*i aurait dû être la limite inférieure lors de la recherche de x*(i+1) au lieu du très premier élément du tableau.)

14
tobias_k

Commentaire de HT @ tobias_k.

Vous pouvez le résoudre en ~ O(n/x) (mettre à jour cela pourrait être en fait O(N*log(N)/X^2)). Vous effectuez une recherche binaire de tous les multiples de x simultanément. Lorsque vous subdivisez chaque espace de recherche à chaque itération et lorsque cet espace de recherche ne peut pas contenir un multiple de x, vous abandonnez cette opération. Ainsi, plutôt que de rechercher chaque valeur binaire, vous recherchez toutes les valeurs, mais uniquement pour les branches qui contiennent encore un multiple valide dans leur plage. La meilleure chose à ce sujet est que cela empêche complètement de rechercher deux fois le même espace, ce qui rend le cas le plus défavorable x = 1 ou O(n/1) O(n). Dans le meilleur des cas, il saura que la plage ne peut pas contenir un multiple et abandonner dans O (1).

Étant donné que vous êtes assuré d'un pire cas de O(n) dans lequel vous ratez pratiquement chaque recherche de cache (gardez à l'esprit que cela pourrait être plus important que la complexité temporelle, testez de telles choses). Vous obtiendrez une complexité de temps théorique qui pourrait être meilleure que O(n) mais jamais pire (sauf que sauter d'un tableau va manquer de caches, car c'est ainsi que les ordinateurs fonctionnent réellement dans le monde réel).

Comme prévu, l'augmentation de la vitesse dépend beaucoup de la valeur de k (x).

Cela commence à aller plus vite que la boucle brute à k = ~ 128. (facteur de division)  

Tronquer les branches réussit à surpasser la boucle brute du monde réel. Je suppose que le nombre de points ne va pas avoir beaucoup d’importance car il semble avoir une échelle à peu près identique, mais vérifier directement que c’est mieux.

Remarque: de par la nature de ce code, il sautera des doublons, ce qui correspond à la différence entre les comptages.

public class MultipleSearch {

    public static void main(String[] args) {
        Random random = new Random();
        int[] array = new int[500000000];
        for (int i = 0, m = array.length; i < m; i++) {
            array[i] = Math.abs(random.nextInt());
        }
        Arrays.sort(array);
        for (int k = 1; k < 16777216; k *= 2) {
            long time;
            time = System.currentTimeMillis();
            binaryFactorLocator(array, k);
            System.out.println("Factors found multi: " + (System.currentTimeMillis() - time) + " @" + k);
            time = System.currentTimeMillis();
            loopFactorLocator(array, k);
            System.out.println("Factors found loop: " + (System.currentTimeMillis() - time) + " @" + k);
        }
    }

    public static void loopFactorLocator(int[] array, int k) {
        int count = 0;
        for (int i = 0, m = array.length; i < m; i++) {
            if (array[i] % k == 0) {
                count++;
                //System.out.println("loop: " + array[i] + " value at index " + i + " is a proven multiple of " + k);
            }
        }
        System.out.println(count + " items found.");
    }

    public static void binaryFactorLocator(int[] array, int k) {
        int count = binaryFactorLocator(0, array, k, 0, array.length);
        System.out.println(count + " items found.");
    }

    public static int binaryFactorLocator(int count, int[] array, int k, int start, int end) {
        if (start >= end) { //contains zero elements. (End is exclusive)
            return count;
        }
        int startValue = array[start]; //first value
        int endValue = array[end - 1]; //last value;
        if (startValue / k == endValue / k) { //if both values are contained within the same factor loop.
            if (startValue % k == 0) { //check lower value for being perfect factor.
                //System.out.println("multi-binary: " + startValue + " value at index " + start + " is a proven multiple of " + k);
                return count + 1;
            }
            return count; //There can be no other factors within this branch.
        }
        int midpoint = (start + end) / 2; //subdivide
        count = binaryFactorLocator(count, array, k, start, midpoint); //recurse.
        count = binaryFactorLocator(count, array, k, midpoint, end); //recurse.
        return count;
    }
}

Cette implémentation devrait être assez solide, car elle tronque la boucle entre les éléments start/k == end/k, elle doit sauter double (parfois, elle peut se séparer de deux valeurs doublées). Évidemment, ce type de récurrence ne sera probablement pas optimal et devrait peut-être être réécrit avec une pile moins de pile d'appels.

474682772 items found.
Factors found multi: 21368 @1
500000000 items found.
Factors found loop: 5653 @1
236879556 items found.
Factors found multi: 21573 @2
250000111 items found.
Factors found loop: 7782 @2
118113043 items found.
Factors found multi: 19785 @4
125000120 items found.
Factors found loop: 5445 @4
58890737 items found.
Factors found multi: 16539 @8
62500081 items found.
Factors found loop: 5277 @8
29399912 items found.
Factors found multi: 12812 @16
31250060 items found.
Factors found loop: 5117 @16
14695209 items found.
Factors found multi: 8799 @32
15625029 items found.
Factors found loop: 4935 @32
7347206 items found.
Factors found multi: 5886 @64
7812362 items found.
Factors found loop: 4815 @64
3673884 items found.
Factors found multi: 3441 @128
3906093 items found.
Factors found loop: 4479 @128
1836857 items found.
Factors found multi: 2100 @256
1953038 items found.
Factors found loop: 4592 @256
918444 items found.
Factors found multi: 1335 @512
976522 items found.
Factors found loop: 4361 @512
459141 items found.
Factors found multi: 959 @1024
488190 items found.
Factors found loop: 4447 @1024
229495 items found.
Factors found multi: 531 @2048
243961 items found.
Factors found loop: 4114 @2048
114715 items found.
Factors found multi: 295 @4096
121964 items found.
Factors found loop: 3894 @4096
57341 items found.
Factors found multi: 195 @8192
61023 items found.
Factors found loop: 4061 @8192
28554 items found.
Factors found multi: 106 @16384
30380 items found.
Factors found loop: 3757 @16384
14282 items found.
Factors found multi: 65 @32768
15207 items found.
Factors found loop: 3597 @32768
7131 items found.
Factors found multi: 35 @65536
7575 items found.
Factors found loop: 3288 @65536
3678 items found.
Factors found multi: 17 @131072
3883 items found.
Factors found loop: 3281 @131072
1796 items found.
Factors found multi: 13 @262144
1900 items found.
Factors found loop: 3243 @262144
873 items found.
Factors found multi: 6 @524288
921 items found.
Factors found loop: 2970 @524288
430 items found.
Factors found multi: 3 @1048576
456 items found.
Factors found loop: 2871 @1048576
227 items found.
Factors found multi: 2 @2097152
238 items found.
Factors found loop: 2748 @2097152
114 items found.
Factors found multi: 1 @4194304
120 items found.
Factors found loop: 2598 @4194304
48 items found.
Factors found multi: 0 @8388608
51 items found.
Factors found loop: 2368 @8388608
13
Tatarize

Lors d'une précédente tentative, j'avais essayé une recherche dichotomique simple mais, comme on l'a fait remarquer, cela ne menait nulle part.

Voici ma meilleure tentative. Je doute que cela en vaille la peine, et que cela puisse même être plus lent pour des cas réels, mais voilà.

Si vous avez un tableau A [0..n] d’entiers positifs triés et positifs et que vous voulez vérifier s’il existe un multiple d’un entier positif X dans A [i..j], où 0≤i

If i>j then A[i..j] ist empty and thus contains no multiple of X
Else if A[i] % X = 0 or A[j] % X = 0 then A[i..j] contains a multiple of X
Else if A[i] / X = A[j] / X then A[i..j] contains no multiple of X
Else A[i..j] contains a multiple of X iff A[i+1..(i+j)/2] or A[(i+j)/2+1..j-1] contains a multiple of X

Je suppose que la complexité serait de O(n/X), donc pas vraiment une amélioration dans le grand schéma des choses.

Modifié pour ajouter: .__ Si vos données sont vraiment particulières, cela pourrait en fait aider. Dans de nombreux cas, cela pourrait même faire mal:

  • il y a la surcharge de la gestion de la pile de retour (le premier appel récursif est non-terminal)
  • parce que nous sautons dans les données au lieu de les parcourir, nous détruisons la localité du cache
  • nous rendons la prédiction de branche plus difficile pour le processeur
6
Zen

Je crois que la réponse à la question générale est non. Supposons que la structure suivante pour A: le i 'ème élément de A ai soit donné par i*x +1 et qu'il n'y ait donc aucun élément dans A, qui soit un multiple de x. Cependant, vous ne gagnez jamais du temps en utilisant l'une des "ruses" décrites ci-dessus ... vous devez fondamentalement choisir en fonction de votre connaissance de A et x. Vous pouvez choisir entre O(N) et O(K) où K est le nombre de multiples possibles de x dans A, vous pouvez obtenir O(K) en utilisant un hachage, tel que le i*x in A devienne une opération à temps constant (en moyenne ...).

1
Chris

Soustrayez x de A de manière itérative. Arrêtez si un résultat est égal à 0.

En ce qui concerne les soustractions, vous le faites de la sorte 1 + {max(A) DIV x} au pire scénario de cette façon, en ce sens que vous devez soustraire x de l'élément maximum de A après que tous les autres ont déjà échoué le contrôle, lui-même, qui échoue également, comme 7 DIV 3 = 2, donc en trois itérations:

  1. 7 - 3 = 4
  2. 4 - 3 = 1
  3. 1 - 3 = -2,! = 0 donc ce n'est pas Divisible

Ceci est toujours qualifié de O(n), mais finit par être rapide, en effectuant une soustraction d’entier sur un tableau.

0

Deux choses à essayer - commencez par trouver le premier élément du tableau supérieur à x. Pas besoin de chercher dans la moitié inférieure. Une recherche binaire peut le faire. Cela réduira le temps dans certains cas, mais si le tout premier élément est supérieur à x, alors nul besoin. Puis, après avoir identifié la section avec des multiples possibles de x, effectuez une recherche binaire si vous exécutez un seul thread ou si vous pouvez exécuter plusieurs threads, divisez la moitié supérieure en segments et effectuez une recherche de thread distincte. Je pense que cela peut être le moyen le plus efficace, mais sous réserve des mises en garde suivantes. 

  1. Si le premier élément supérieur à x est assez bas dans le tableau, vous passerez plus de temps à configurer la recherche binaire qu'au balayage linéaire. 

  2. Faire des recherches binaires a aussi un coût. Si votre tableau n'est pas très grand, vous serez moins efficace que les recherches linéaires. Je ne parle pas de l'ordre de l'algorithme. Je considère juste le temps d'exécution.

  3. Si vous pouvez créer plusieurs threads, leur mise en place entraîne également un coût assez lourd. À moins que votre tableau ne soit obscurément long, vous ne gagnerez peut-être aucun avantage à le faire aussi. Toutefois, si le contenu est long de plusieurs millions d’articles, vous pouvez en bénéficier en le divisant en morceaux plus petits. Je dirais qu'il serait rare que ce scénario soit utile.

0
Santanu Lahiri