7
votes

Énumérer des points de grille sur un plan 2D avec ordre décroissant de (x * y)

donné n> 0 et m> 0 , je veux énumérer toutes les paires (x, y) telles que 1 <= x <= n et 1 <= y <= m en ordre décroissant de (x * y). Un exemple: donné N = 3 et M = 2, la séquence de dénombrement doit être: xxx

l'ordre de (2, 1) et (1 , 2) pourrait être échangé. Un moyen évident consiste à les énumérer tous, insérer dans un vecteur > et appelez std :: Trier () avec ma propre fonction de comparaison. Cependant, étant donné que N et M peuvent être importants et la plupart du temps, je n'ai besoin que des premiers termes de la séquence, j'espère qu'il pourrait y avoir une manière plus intelligente qui génère une telle séquence au lieu de les générer tout-et-genre, qui Nécessite autant que les éléments n * m array.

MISE À JOUR: J'ai oublié de mentionner que bien que la plupart du temps, je n'ai besoin que des premiers termes, le nombre de termes requis est inconnu avant l'énumération.


1 commentaires

Pour les réponses potentielles: ne faites pas la même erreur que je l'ai fait - essayez votre réponse sur papier en premier!


8 Réponses :


0
votes

Ceci est effectivement équivalent à énumérer les nombres premiers; Les chiffres que vous souhaitez sont tous les numéros qui ne sont pas primordiaux (à l'exception de tous ceux qui ont x ou y égal à 1).

Je ne suis pas sûr qu'il y ait un méthode d'énumération des primes qui va être plus rapide que ce que Vous proposez déjà (au moins en termes de complexité algorithmique).


5 commentaires

"Les chiffres que vous voulez sont tous les chiffres qui ne sont pas primordiaux" semble faux. 3 et 2 sont dans l'exemple.


@andrewkeke: C'est vrai. OK, lorsque vous avez tiré en tant que grille de multiplication 2D, ignorez la première rangée et la première colonne, tout le reste est un non-premier, je pense!


Si (et seulement si) x ou y (ou les deux) dans (x, y) est 1, x * y < / code> peut être un premier. Mais de toute façon comment générerez-vous les paires? Vous devrez factoriser tous ces non-premes, non? Ça ne sonne pas trop attrayant pour moi ..


@HAROLD: Le point que j'essayais de faire est que, dans le cas général, je ne pense pas qu'il y ait un algorithme "rapide" pour cela. Cependant, comme cela a été signalé dans d'autres réponses, si le nombre de valeurs souhaité est petit, il y a de meilleures techniques (et je suis donc sur le point de supprimer ma réponse!).


C'est faux. Vous suggérez de trouver les valeurs dans la grille en énumérant les nombres premiers, probablement en les soustrant de la séquence 1,2, ..., n * m . Considérez n = 247, m = 6, n * m = 1482. Pourtant, 1477 = 211 * 7 est composite, mais il n'est pas présent dans cette grille. - Peut-être que votre méthode fonctionnerait dans le cas M == N?



0
votes

Parce que vous mentionnez que la plupart du temps, vous avez besoin des premiers termes de la séquence; Après les avoir générés, tout ce que vous n'avez pas besoin de les trier tous pour trouver ces premiers termes. Vous pouvez utiliser un tas Max en fonction du nombre de termes que vous souhaitez, dites k. Donc, si un tas est de taille k (<< n && << m), vous pouvez avoir des plus grands termes K après Nlogk, ce qui est meilleur que Nlogn pour le tri.

ici n = n * m


3 commentaires

Avez-vous besoin de générer tout le tas? N'y a-t-il aucun moyen d'avoir une idée du degré auquel le tas est complet à un moment donné de l'expansion?


Je ne pense pas que cela soit vrai. Afin de s'assurer que votre tas inclinait les valeurs maximales k , vous devez vous assurer que toutes les valeurs sont dans le tas. Qui ramène votre temps à n journal n .


Voir les autres messages pour la façon dont vous pourriez le faire (si je comprends bien). Par exemple, pour K = 1, vous savez certainement.



6
votes

Si vous cherchez simplement à économiser sur l'espace tout en conservant le temps comme étant plus ou moins égal, vous pouvez compter sur le fait que chaque élément réussi plus petit doit être adjacent (dans la grille 2-D que vous avez allusion à). des éléments que vous avez déjà rencontrés. (Vous pouvez le prouver avec induction, ce n'est pas particulièrement difficile. Je vais assumer pour le reste de ceci que m> = n.)

L'algorithme de base ressemble à quelque chose comme: xxx < / Pré>

Vous pouvez répéter cela aussi longtemps que vous voulez plus d'éléments de points énumérées. La taille maximale des candidats doit être m + n, donc je pense que c'est O (k Log (m + n)) où K est le nombre de points que vous souhaitez.

addendum: La question de l'évichement du duplicata n'est pas tout à fait difficile mais mérite d'être mentionnée. Je suppose dans cet algo que vous posez votre grille de sorte que les chiffres descendent lorsque vous descendez et à droite. Quoi qu'il en soit, il va comme ça:

au début de l'algorithme crée une matrice (taille de colonne) qui a un élément pour chaque colonne. Vous devez contenir ce tableau contenant le nombre de lignes dans chaque colonne faisant partie de la liste des points énumérées.

Après avoir ajouté un nouvel élément et mettre à jour ce tableau, vous vérifierez la taille de la colonne sur la colonne. de chaque côté afin de décider si la grille des carrés au droit immédiat et ci-dessous de ce nouveau point énuméré est déjà dans la liste des candidats.

Vérifiez la taille de la colonne à gauche, si elle est supérieure à celle-ci. un, vous n'avez pas besoin d'ajouter l'élément sous votre nouveau point énuméré.

Vérifiez la taille de la colonne à droite, si elle est inférieure à la même taille de cette colonne, vous ne le faites pas Besoin de mettre à jour l'élément à droite de celui-ci.

Pour faire cela évident, regardons cette carte partiellement remplie de m = 4, n = 2: xxx

Les éléments (4,2), (3,2), (2,2) et (4,1) sont déjà dans la liste. (La première coordonnée est m, la seconde est N.) La matrice de taille de colonne est [2 1 1 0], car il s'agit du nombre d'éléments de chaque colonne figurant dans la liste des points énumérées. Nous sommes sur le point d'ajouter (3,1) à la nouvelle liste, nous pouvons regarder la taille de la colonne à droite et conclure que l'ajout (2,1) n'est pas nécessaire car la taille de la colonne de m = 2 est plus grande. que 1-1. Le raisonnement est assez clair visuellement - nous avons déjà ajouté (2,1) lorsque nous avons ajouté (2,2).


11 commentaires

Cela semble que ce serait beaucoup plus rapide (pour le grand nombre de valeurs), car vous pouvez conserver une liste triée de voisins qui donne la valeur la plus basse suivante, de sorte que vous sachiez quand aller plus loin.


Je ne voulais pas dire adjacent à l'élément le plus récent, mais adjacent à l'un des éléments que vous avez séquencé jusqu'à présent


@airza: Dans ce cas, je suis d'accord avec Andrew. Pour les petites valeurs de k (nombre de valeurs requises), cela va probablement impliquer de manière significative moins de calcul / mémoire que l'une des alternatives jusqu'à présent. Pour grand k , il est moins clair s'il y a un avantage.


Je travaille toujours à travers les mathématiques, mais cela semble être une mémoire efficace et effectuer le même nombre de calculs. Y a-t-il un moyen efficace de dessiner des grilles?


La consommation de mémoire de cette approche est O (n + m) (au pire, la "frontière" que vous devez évaluer va de l'élément supérieur gauche à travers le bas à droite, sa longueur est donc la distance de Manhattan entre ces deux éléments ). Vous devez bien trier ces éléments.


J'ai modifié l'approche quelque peu pour que j'espère, à le présenter de manière infinie et légèrement plus rapide. Je soupçonne que la taille maximale de la distance frontière est m avec M> n, mais à court de papier à gratter pour essayer de le regarder.


Oui, vous devez choisir uniquement le plus haut élément non retiré de chaque rangée.


Cela a la légère faille que tous les points non bord sont insérés dans votre liste de candidats deux fois - une fois par le point au-dessus d'eux, et une fois par le point à leur droite. N'affectera pas la précision, mais vous obtiendrez une vitesse théorique 2x si vous pouviez éviter cela. J'ai une réponse plus bas qui le fait que en travaillant uniquement dans des colonnes - vous pourriez peut-être adapter cette approche?


J'ai déjà pris une note de ne pas ajouter les valeurs si elles sont déjà dans la liste - il existe une variété de façons d'éviter ce destin.


Les candidats n'auront que des éléments O (k), car vous ne comprenez que 2K fois. Votre approche est donc O (k Logk) non O (kog (m + n)). :)


Ah ouais, je n'ai pas recalculé la vitesse.



1
votes

Voici une solution O (k Logk), où K est le nombre de termes que vous souhaitez générer.
EDIT: Q ne conserve qu'une copie de chaque élément; Un insert échoue si l'élément est déjà présent.

priority_queue Q
Q.insert( (N*M, (N,M)) ) // priority, (x,y) 
repeat K times:
    (v, (x,y)) = Q.extract_max()
    print(v, (x,y))
    Q.insert( (x-1)*y, (x-1,y) )
    Q.insert( x*(y-1), (x,y-1) )


4 commentaires

Vous insérez beaucoup d'éléments plusieurs fois.


@airza: J'ai supposé que le priority_queue n'insère pas d'élément s'il est déjà présent. Comme STD :: SET EN C ++.


Oh, hé, merci pour cela- Je ne savais pas qu'il y avait une meilleure structure qu'un arbre de saut pour ce problème.


Je ne pensais pas que la table de hachage était utile car elle devrait avoir la même taille que l'ensemble de la matrice.



0
votes

Une approche factice qui boucle de NXM à 1 recherche des paires qui, lorsqu'elles sont multipliées produisent le nombre actuel: xxx

pour n = m, la complexité est O (n 3 ) mais l'utilisation de la mémoire est O (1).

Mise à jour : Notez que la complexité n'est pas aussi mauvaise que parce que le nombre d'éléments à générer est déjà n ° N < sup> 2 . Pour la comparaison, l'approche Generate-The-The-Sort est O (N 2 logn) avec O (N 2 ) Utilisation de la mémoire.


0 commentaires

0
votes

Voici un algorithme pour vous. Je vais essayer de vous donner une description en anglais.

Dans le rectangle, nous travaillons avec, nous pouvons toujours supposer qu'un point p (x, y) code> a une plus grande "zone" que Le point ci-dessous p (x, y-1) code>. Ainsi, lorsque vous recherchez des points de surface maximale, il suffit de comparer le point le plus haut absolu dans chaque colonne (c'est-à-dire pour chaque x code>). Par exemple, lorsque vous envisagez de la pristine 3 x 5 code> grille p> xxx pré>

Nous n'avons vraiment besoin que de comparer A code>, B code> et c code>. Tous les autres points sont garantis pour avoir moins de superficie qu'au moins une de celles-ci. Donc, construisez un tas maximum qui ne contient que le point le plus haut dans chaque colonne. Lorsque vous faites obstacle à un point sur le tas, poussez-le au point qui se trouve directement en dessous (si ce point existe). Répétez jusqu'à ce que le tas soit vide. Cela vous donne l'algorithme suivant (testé, mais c'est en rubis): p> xxx pré>

ceci vous donne une heure d'exécution de O ((2K + N) log n) code>. Coût des opérations de tas journal n code>; nous faisons n code> lors de la construction du tas initial, puis 2k code> lorsque nous retirons les points k code> de la surface maximale (2 en supposant chaque pop est suivie de la poussée du point ci-dessous). P>

Il a l'avantage supplémentaire de ne pas avoir à construire tous vos points, contrairement à la proposition initiale de la construction de l'ensemble de l'ensemble, puis de tri. Vous ne construisez que autant de points que nécessaire pour garder votre tas précis. P>

et enfin: les améliorations peuvent être faites! EM> Je ne les ai pas faites, mais vous pourriez avoir une meilleure performance avec les modifications suivantes: p>

  1. ne descend que sur y = x code> dans chaque colonne au lieu de y = 1 code>. Pour générer les points que vous ne vérifiez plus, utilisez le fait que la zone de p (x, y) code> est égale à la zone de p (y, x) code >. REMARQUE: FORT> Si vous utilisez cette méthode, vous aurez besoin de deux versions de l'algorithme. Les colonnes fonctionnent lorsque m> = n code>, mais si m vous devez le faire par des lignes à la place. Li>
  2. considère uniquement les colonnes qui pourraient éventuellement contenir le maximum. Dans l'exemple que j'ai donné, il n'y a aucune raison d'inclure A code> dans le tas depuis le début, car il est garanti d'être inférieur à B code>. Donc, il vous suffit d'ajouter des colonnes au tas lorsque les points supérieurs de leurs colonnes de voisin sont sautés. Li> ol>

    et qui s'est transformé en un petit essai ... de toute façon - espérons que cela aide! p>

    EDIT: FORT> L'algorithme complet, intégrant les deux améliorations que j'ai mentionnées ci-dessus ( Mais toujours dans Ruby, parce que je suis paresseux). Notez qu'il n'est pas nécessaire de ne pas avoir besoin de structures supplémentaires pour éviter d'insérer des doublons - à moins que ce soit un "sommet", chaque point ne insérera qu'un autre point de sa ligne / colonne est pris. P>

    def enumerate(n, m, k)
        heap = MaxHeap.new
        heap.push(Point.new(n, m))
        result = []
    
        loop do
            max = heap.pop
            result << max
            return result if result.length == k
    
            result << Point.new(max.y, max.x) if max.x <= m && max.y <= n && max.x != max.y
            return result if result.length == k
    
            if(m < n) # One point per row
                heap.push(Point.new(max.x, max.y - 1)) if max.x == n && max.y > 1
                heap.push(Point.new(max.x - 1, max.y)) if max.x > max.y
            else # One point per column
                heap.push(Point.new(max.x - 1, max.y)) if max.y == m && max.x > 1
                heap.push(Point.new(max.x, max.y - 1)) if max.y > max.x
            end
        end
    end
    


2 commentaires

Ce n'est pas tellement différent de la solution d'Airza.


@akappa - En effet c'est (mais je l'ai marqué seul!). Je lui ai donné une bosse pour l'esprit comme l'esprit.



0
votes

Je l'ai compris!

Considérez la grille en tant que jeu de m colonnes où chaque colonne est une pile contenant les éléments de 1 en bas à n en haut en haut. Chaque colonne est étiquetée avec sa coordonnée x.

Les éléments à l'intérieur de chaque pile de colonne sont commandés par sa valeur y et aussi par x * y comme x a la même valeur pour toutes.

Donc, il vous suffit d'aller choisir la pile qui a la plus grande valeur x * y à son sommet, pop-le et répétez.

En pratique, vous n'aurez pas besoin de piles, juste l'index du Valeur supérieure et vous pouvez utiliser une file d'attente prioritaire pour obtenir la colonne avec la plus grande valeur x * y. Ensuite, décrémenter la valeur de l'indice et s'il est plus grand que 0 (indiquant que la pile n'a pas été épuisée) réinsérez la pile sur la file d'attente avec sa nouvelle priorité x * y.

la complexité de cette L'algorithme pour n = m est O (n 2 logn) et son utilisation de la mémoire O (n).

update : mis en œuvre dans Perl ... xxx


0 commentaires

0
votes

in Haskell, il produit la sortie immédiatement forte>. Voici une illustration:

Prelude Main> take 10 $ drop 50000 $ map (log.fromIntegral.fst) $ enuNM (2*10^8)
 (3*10^8)
[38.633119670465554,38.633119670465554,38.63311967046555,38.63311967046554,38.63
311967046554,38.63311967046553,38.63311967046553,38.633119670465526,38.633119670
465526,38.63311967046552]
(0.17 secs, 35425848 bytes)

Prelude Main> take 10 $ drop 100000 $ map (log.fromIntegral.fst) $ enuNM (2*10^8
) (3*10^8)
[38.63311913546512,38.633119135465115,38.633119135465115,38.63311913546511,38.63
311913546511,38.6331191354651,38.6331191354651,38.633119135465094,38.63311913546
5094,38.63311913546509]
(0.36 secs, 71346352 bytes)

*Main> let x=it
*Main> zipWith (>=) x (tail x)
[True,True,True,True,True,True,True,True,True]

Prelude Main> logBase 2 (0.36/0.17)
1.082462160191973     -- O(n^1.08) for n=100000 values produced


0 commentaires