On vous donne un tableau de éléments
La tâche consiste à entrelacer le tableau, en utilisant un algorithme sur place tel que le tableau résultant ressemble à
Si l'exigence en place n'existait pas, nous pourrions facilement créer un nouveau tableau et copier des éléments en donnant un algorithme time.
Avec l'exigence en place, un algorithme de division et de conquête oblige l'algorithme à devenir .
La question est donc:
Existe-t-il un algorithme time, qui est également en place?
(Remarque: vous pouvez supposer que le modèle WORD RAM à coût uniforme est utilisé, donc in-situ se traduit par une restriction d'espace ).
algorithms
arrays
permutations
in-place
Aryabhata
la source
la source
Réponses:
Voici la réponse qui développe l'algorithme à partir du document lié par Joe: http://arxiv.org/abs/0805.1598
Considérons d’abord un algorithme qui utilise diviser pour régner.Θ(nlogn)
1) Diviser et conquérir
On nous donne
Maintenant, pour utiliser diviser pour régner, pour certains , nous essayons d'obtenir le tableaum=Θ(n)
et recurse.
Notez que la partie est un décalage cyclique deb1,b2,…bm,am+1,…an
par lieux.m
Ceci est un classique et peut être fait sur place par trois retournements et en time.O(n)
Ainsi, la division et la conquête vous donne un algorithme , avec une récursion similaire à .Θ(nlogn) T(n)=2T(n/2)+Θ(n)
2) Cycles de permutation
Maintenant, une autre approche du problème consiste à considérer la permutation comme un ensemble de cycles disjoints.
La permutation est donnée par (en partant de )1
Si nous savions exactement quels étaient les cycles, en utilisant un espace supplémentaire constant, nous pourrions réaliser la permutation en choisissant un élément , déterminer où cet élément est placé (à l'aide de la formule ci-dessus), mettre l'élément dans l'emplacement cible dans un espace temporaire, l'élément dans cet emplacement cible et continue le long du cycle. Une fois que nous avons terminé avec un cycle, nous passons à un élément du cycle suivant et suivons ce cycle, etc.A A
Cela nous donnerait un algorithme temporel, mais cela suppose que nous "sachions en quelque sorte quels étaient les cycles exacts" et que nous essayions de faire cette comptabilité dans la limite d'espace est ce qui rend ce problème difficile.O(n) O(1)
C'est ici que le papier utilise la théorie des nombres.
On peut montrer que, dans le cas où , les éléments aux positions , sont dans des cycles différents et chaque cycle contient un élément à la position .2n+1=3k 1 3,32,…,3k−1 3m,m≥0
Ceci utilise le fait que est un générateur de .2 (Z/3k)∗
Ainsi, lorsque , l'approche par cycle nous donne un algorithme temporel , car pour chaque cycle, nous savons exactement par où commencer: puissances de (y compris ) peut être calculé dans l' espace ).2n+1=3k O(n) 3 1 O(1)
3) algorithme final
Maintenant, nous combinons les deux précédents: Diviser et conquérir + Cycles de permutation.
Nous faisons une division et conquérons, mais choisissons pour que soit une puissance de et .m 2m+1 3 m=Θ(n)
Donc, au lieu de revenir sur les deux "moitiés", nous ne récrivons qu'une seule et faisons un travail supplémentaire.Θ(n)
Ceci nous donne la récurrence (pour quelque ) et nous donne donc un temps , algorithme spatial!T(n)=T(cn)+Θ(n) 0<c<1 O(n) O(1)
la source
Je suis presque sûr d'avoir trouvé un algorithme qui ne repose pas sur la théorie des nombres ou la théorie des cycles. Notez qu'il y a quelques détails à régler (peut-être demain), mais je suis assez confiant. Je fais la main comme je suis supposé dormir, pas parce que j'essaie de cacher des problèmes :)
Laissez -
A
être le premier tableau,B
le second,|A| = |B| = N
et d' assumerN=2^k
pour certainsk
, pour simplifier. SoitA[i..j]
le sous-tableau deA
avec indicesi
jusqu'àj
, inclus. Les tableaux sont basés sur 0. RetournonsRightmostBitPos(i)
la position (basée sur 0) du bit le plus à droite qui est '1'i
, en comptant à partir de la droite. L'algorithme fonctionne comme suit.Prenons un tableau de 16 nombres, commençons simplement à les entrelacer à l'aide de swaps et voyons ce qui se passe:
La première partie du second tableau est particulièrement intéressante:
Le modèle doit être clair: nous ajoutons un nombre à la fin et remplaçons le nombre le plus bas par un nombre élevé. Notez que nous ajoutons toujours un nombre supérieur au nombre le plus élevé que nous avons déjà. Si nous étions en mesure de déterminer exactement quel nombre est le plus bas à un moment donné, nous pouvons le faire facilement.
Maintenant, nous allons pour des exemples plus grands pour voir si nous pouvons voir un motif. Notez qu'il n'est pas nécessaire de fixer la taille du tableau pour construire l'exemple ci-dessus. À un moment donné, nous obtenons cette configuration (la deuxième ligne soustrait 16 de chaque nombre):
Maintenant, cela montre clairement un motif: "1 3 5 7 9 11 13 15" sont tous 2 séparés, "2 6 10 14" sont tous 4 séparés et "4 12" sont 8 séparés. Nous pouvons donc concevoir un algorithme qui nous dit quel sera le prochain nombre le plus petit: le mécanisme est à peu près exactement le fonctionnement des nombres binaires. Vous avez un peu pour la dernière moitié du tableau, un peu pour le deuxième trimestre et ainsi de suite.
Si nous disposons donc de suffisamment d’espace pour stocker ces bits (nous avons besoin de bits, mais notre modèle de calcul le permet - un pointeur dans le tableau a également besoin de bits), nous pouvons déterminer le nombre à échanger dans temps amorti.logn logn O(1)
On peut donc obtenir la première moitié du tableau dans son état entrelacée dans temps et swaps. Cependant, nous devons réparer la seconde moitié de notre tableau, qui semble tout foiré ("8 6 5 7 13 14 15 16").O(n) O(n)
Maintenant, si nous pouvons "trier" la première moitié de cette seconde partie, nous aboutissons à "5 6 7 8 13 14 15 16", et entrelacer récursivement cette moitié fera l'affaire: nous entrelacez le tableau en time ( appels récursifs dont chacun divise par deux la taille de l’entrée). Notez que nous n'avons pas besoin d'une pile car ces appels sont récursifs, notre utilisation de l'espace reste donc .O(n) O(logn) O(1)
Maintenant, la question est la suivante: existe-t-il un motif dans la partie à trier? Essayer 32 numéros nous donne "16 12 10 14 9 11 13 15" à corriger. Notez que nous avons exactement le même modèle ici! "9 11 13 15", "10 14" et "12" sont regroupés de la même manière que nous avons vue précédemment.
Maintenant, l'astuce consiste à entrelacer récursivement ces sous-parties. Nous entrelacez "16" et "12" en "12 16". Nous entrelacez "12 16" et "10 14" avec "10 12 14 16". Nous entrelacez "10 12 14 16" et "9 11 13 15" pour "9 10 11 12 13 14 15 16". Cela trie la première partie.
Comme ci-dessus, le coût total de cette opération est . En additionnant tout cela, nous parvenons tout de même à obtenir un temps d'exécution total de .O(n) O(n)
Un exemple:
la source
Voici un algorithme linéaire non temporel in-récursif pour entrelacer deux moitiés d'un tableau sans stockage supplémentaire.
L'idée générale est simple: parcourez la première moitié du tableau de gauche à droite en échangeant les valeurs correctes. Au fur et à mesure que vous avancez, les valeurs restantes non encore utilisées sont échangées dans l'espace laissé vacant par les valeurs correctes. La seule astuce consiste à déterminer comment les retirer à nouveau.
Nous commençons avec un tableau de taille N divisé en 2 moitiés presque égales.
[ left_items | right_items ]
En le traitant, il devient
[ placed_items | remaining_left_items| swapped_left_items | remaining_right_items]
L'espace d'échange croît avec le motif suivant: A) agrandit cet espace en supprimant l'élément de droite adjacent et en échangeant un nouvel élément à gauche; B) permutez l’objet le plus ancien avec un nouvel élément à gauche. Si les éléments de gauche sont numérotés 1..N, ce modèle ressemble à
La séquence d'indexation modifiée correspond exactement à OEIS A025480 , qui peut être calculée à l'aide d'un processus simple. Cela permet de trouver l'emplacement de permutation compte tenu du nombre d'éléments ajoutés jusqu'à présent, qui correspond également à l'index de l'élément en cours de placement.
C'est toute l'information dont nous avons besoin pour renseigner la première moitié de la séquence en temps linéaire.
Lorsque nous arriverons au point médian, le tableau comportera trois parties:
[ placed_items | swapped_left_items | remaining_right_items]
si nous pouvons déchiffrer les éléments échangés, nous avons réduit le problème à la moitié de la taille et nous pouvons répéter.Pour déchiffrer l’espace de permutation, nous utilisons la propriété suivante: Une séquence construite en
N
alternant les opérations append et swap_oldest contiendra desN/2
éléments pour lesquels leur âge est donnéA025480(N/2)..A025480(N-1)
. (Division entière, les plus petites valeurs sont plus anciennes).Par exemple, si la moitié gauche contenait à l’origine les valeurs 1..19, l’espace de permutation contiendrait
[16, 12, 10, 14, 18, 11, 13, 15, 17, 19]
. A025480 (9..18) est[2, 5, 1, 6, 3, 7, 0, 8, 4, 9]
, qui est exactement la liste des index des éléments du plus ancien au plus récent.Ainsi, nous pouvons déchiffrer notre espace de swap en le parcourant et en le permutant
S[i]
avecS[ A(N/2 + i)]
. C'est aussi le temps linéaire.La complication restante est que vous finirez par atteindre une position où la valeur correcte devrait être à un indice inférieur, mais elle a déjà été remplacée. Il est facile de trouver le nouvel emplacement: il suffit de refaire le calcul de l'index pour savoir où l'élément a été échangé. Il peut être nécessaire de suivre la chaîne quelques étapes jusqu'à ce que vous trouviez un emplacement non échangé.
À ce stade, nous avons fusionné la moitié du tableau et conservé l'ordre des parties non fusionnées dans l'autre moitié, avec exactement des
N/2 + N/4
échanges. Nous pouvons continuer à travers le reste du tableau pour un total deN + N/4 + N/8 + ....
swaps strictement inférieur à3N/2
.Comment calculer A025480:
Ceci est défini dans OEIS comme étant
a(2n) = n, a(2n+1) = a(n).
une formulation alternativea(n) = isEven(n)? n/2 : a((n-1)/2)
. Cela conduit à un algorithme simple utilisant des opérations au niveau du bit:Il s’agit d’une opération O (1) amortie sur toutes les valeurs possibles pour N. (1/2 besoin 1, équipe 1/4 besoin 2, 1/8 besoin 3, ...) . Il existe une méthode encore plus rapide qui utilise une petite table de correspondance pour trouver la position du bit zéro le moins significatif.
Dans ce cas, voici une implémentation en C:
Cela devrait être un algorithme assez convivial pour le cache, puisque 2 des 3 emplacements de données sont accédés de manière séquentielle et que la quantité de données en cours de traitement diminue strictement. Cette méthode peut être transformée en mélange aléatoire en annulant le
is_even
test au début de la boucle.la source