78 votes

Algorithme pour la fusion N-way

Un 2-way merge est largement étudié comme une partie de Mergesort algorithme. Mais je suis intéressé à trouver la meilleure façon, on peut effectuer un N-chemin de fusion?

Disons que, j'ai N fichiers qui ont trié 1 million de nombres entiers chaque. - Je les fusionner en 1 seul fichier, ce qui permettra d'avoir ces 100 millions triés entiers.

Veuillez garder à l'esprit que les cas d'utilisation pour ce problème est en réalité externe de tri qui est sur disque. Par conséquent, dans des scénarios réels, il y aurait une limitation de la mémoire. Ainsi, une approche naïve de la fusion de 2 fichiers à la fois (99 fois) ne fonctionne pas. Disons que nous avons seulement une petite fenêtre coulissante de mémoire disponible pour chaque tableau.

Je ne sais pas si il existe déjà une solution standardisée à cette N-voie de fusion. (Google ne m'a pas dit grand chose).

Mais si vous savez si un n-chemin algorithme de fusion, s'il vous plaît poster algo/lien.

Complexité temporelle: Si nous avons grandement augmenter le nombre de fichiers (N) à être fusionnés, comment cela affecterait-il le temps de la complexité de votre algorithme?

Merci pour vos réponses.

Je n'ai pas été invité partout, mais j'ai senti cela pourrait être une interview intéressante question. Donc balisé.

80voto

aioobe Points 158466

Comment à propos de l'idée suivante:

  1. Créer une file d'attente de priorité

  2. Itérer sur chaque fichier f
    1. mettre en file d'attente de la paire (nextNumberIn(f), f) à l'aide de la première valeur de priorité clé

  3. Alors que la file d'attente n'est pas vide
    1. retirer de la tête (m, f) de la file d'attente
    2. sortie m
    3. si f ne pas épuiser les réserves
      1. enqueue (nextNumberIn(f), f)

Depuis l'ajout d'éléments à une file d'attente de priorité peut être fait en temps logarithmique, le point 2 est O(N × log N). Depuis (presque tous) les itérations de la boucle while ajoute un élément, le tout en boucle est O(M × log N)M est le nombre total de nombres à trier.

En supposant que tous les fichiers ont une séquence non-vide de nombres, nous avons M > N et donc l'ensemble de l'algorithme O(M × log N).

12voto

Grigori Melnik Points 2676

Recherche pour "Polyphasé de fusion", découvrez des classiques de Donald Knuth & E. H. Ami.

Aussi, vous voudrez peut-être prendre un coup d'oeil à la proposition de Bloc Intelligent Fusion par Seyedafsari & Hasanzadeh, qui, de même pour les suggestions, utilise des files d'attente de priorité.

Un autre fait intéressant reasonsing est En Place la Fusion de l'Algorithme par Kim & Kutzner.

Je recommande également ce papier par Vitter: mémoire Externe algorithmes et structures de données: traitement de données massives.

6voto

templatetypedef Points 129554

Une idée simple est de garder une file d'attente prioritaire de les gammes de fusion, stockées de telle manière que la gamme avec le plus petit premier élément est supprimé de la première de la file d'attente. Vous pouvez ensuite faire un N-chemin de fusion comme suit:

  1. Insérer toutes les plages dans la file d'attente de priorité, à l'exclusion de vide plages.
  2. Alors que la priorité de la file d'attente n'est pas vide:
    1. Retirer le plus petit élément de la file d'attente.
    2. Ajouter le premier élément de cette gamme à la séquence de sortie.
    3. Si c'est non vide, insérez le reste de la séquence de retour dans la file d'attente de priorité.

La correction de cet algorithme est essentiellement une généralisation de la preuve qu'un 2-way fusion fonctionne correctement - si vous ajoutez toujours le plus petit élément de toute la gamme, et toutes les plages sont triés, vous vous retrouvez avec la séquence dans son ensemble trié.

L'exécution de la complexité de cet algorithme peut être trouvé qui suit. Soit M le nombre total d'éléments dans toutes les séquences. Si nous utilisons un tas binaire, alors nous le faisons au plus O(M) insertions et O(M) retrait de la file d'attente de priorité, puisque pour chaque élément écrit à la séquence de sortie il y a une file d'attente de sortir de la plus petite séquence, suivie par une mise en file d'attente pour mettre le reste de la séquence de retour dans la file d'attente. Chacune de ces étapes est O(lg N) opérations, parce que l'insertion ou la suppression d'un tas binaire avec N éléments qu'il prend O(lg N) fois. Cela donne un net runtime de O(M lg N), qui croît moins que linéairement avec le nombre de séquences d'entrée.

Il y a peut être un moyen d'obtenir ce encore plus vite, mais cela semble être une très bonne solution. L'utilisation de la mémoire est O(N) car nous avons besoin de O(N) les frais généraux pour le tas binaire. Si nous mettons en œuvre le tas binaire par stocker des pointeurs vers les séquences plutôt que les séquences elles-mêmes, cela ne devrait pas être trop un problème, sauf si vous avez vraiment ridicule nombre de séquences de fusion. Dans ce cas, il suffit de les fondre dans des groupes qui ne tenir en mémoire, puis de fusionner tous les résultats.

Espérons que cette aide!

2voto

user1908342 Points 21

Une approche simple de la Fusion de k triés tableaux, chacun de longueur n) nécessite O(n k^2) et pas de O(nk). Comme lorsque vous fusionnez les 2 premiers tableaux, il faut 2n fois, puis lors de la fusion de la troisième à la sortie , il prend 3n temps que maintenant, nous sommes à la fusion des deux tableau de longueur 2n et n. Maintenant, quand nous fusionner cette sortie avec le quatrième,cette opération nécessite 4n temps.Ainsi, la dernière fusion (lorsque nous ajoutons de la kième tableau déjà un tableau trié ) nécessite k*n fois.Ainsi, le temps total requis est de 2n+ 3n + 4n +...k*n qui est O(n k^2).

Il semble que nous pouvons le faire en O(kn) moment mais ce n'est pas parce qu'à chaque fois notre tableau dont nous sommes la fusion est en augmentation dans la taille.
Si nous pouvons parvenir à une meilleure liées à l'aide de diviser pour régner. Je travaille toujours sur et post une solution si j'en trouve un.

1voto

Ohad Schneider Points 10485

Voir http://en.wikipedia.org/wiki/External_sorting. Voici mon point de vue sur le tas en fonction k-way merge, à l'aide d'un tampon de lecture à partir des sources d'émuler I/O de réduction:

public class KWayMerger<T>
{
    private readonly IList<T[]> _sources;
    private readonly int _bufferSize;
    private readonly MinHeap<MergeValue<T>> _mergeHeap;
    private readonly int[] _indices;

    public KWayMerger(IList<T[]> sources, int bufferSize, Comparer<T> comparer = null)
    {
        if (sources == null) throw new ArgumentNullException("sources");

        _sources = sources;
        _bufferSize = bufferSize;

        _mergeHeap = new MinHeap<MergeValue<T>>(
                      new MergeComparer<T>(comparer ?? Comparer<T>.Default));
        _indices = new int[sources.Count];
    }

    public T[] Merge()
    {
        for (int i = 0; i <= _sources.Count - 1; i++)
            AddToMergeHeap(i);

        var merged = new T[_sources.Sum(s => s.Length)];
        int mergeIndex = 0;

        while (_mergeHeap.Count > 0)
        {
            var min = _mergeHeap.ExtractDominating();
            merged[mergeIndex++] = min.Value;
            if (min.Source != -1) //the last item of the source was extracted
                AddToMergeHeap(min.Source);
        }

        return merged;
    }

    private void AddToMergeHeap(int sourceIndex)
    {
        var source = _sources[sourceIndex];
        var start = _indices[sourceIndex];
        var end = Math.Min(start + _bufferSize - 1, source.Length - 1);

        if (start > source.Length - 1)
            return; //we're done with this source

        for (int i = start; i <= end - 1; i++)
            _mergeHeap.Add(new MergeValue<T>(-1, source[i]));   

        //only the last item should trigger the next buffered read
        _mergeHeap.Add(new MergeValue<T>(sourceIndex, source[end]));

        _indices[sourceIndex] += _bufferSize; //we may have added less items, 
        //but if we did we've reached the end of the source so it doesn't matter
    } 
}

internal class MergeValue<T>
{
    public int Source { get; private set; }
    public T Value { get; private set; }

    public MergeValue(int source, T value)
    {
        Value = value;
        Source = source;
    }
}

internal class MergeComparer<T> : IComparer<MergeValue<T>>
{
    public Comparer<T> Comparer { get; private set; }

    public MergeComparer(Comparer<T> comparer)
    {
        if (comparer == null) throw new ArgumentNullException("comparer");
        Comparer = comparer;
    }

    public int Compare(MergeValue<T> x, MergeValue<T> y)
    {
        Debug.Assert(x != null && y != null);
        return Comparer.Compare(x.Value, y.Value);
    }
}

Voici une implémentation possible de l' MinHeap<T>. Quelques tests:

[TestMethod]
public void TestKWaySort()
{
    var rand = new Random();
    for (int i = 0; i < 10; i++)
        AssertKwayMerge(rand);
}

private static void AssertKwayMerge(Random rand)
{
    var sources = new[]
        {
            GenerateRandomCollection(rand, 10, 30, 0, 30).OrderBy(i => i).ToArray(),
            GenerateRandomCollection(rand, 10, 30, 0, 30).OrderBy(i => i).ToArray(),
            GenerateRandomCollection(rand, 10, 30, 0, 30).OrderBy(i => i).ToArray(),
            GenerateRandomCollection(rand, 10, 30, 0, 30).OrderBy(i => i).ToArray(),
        };
    Assert.IsTrue(new KWayMerger<int>(sources, 20).Merge().SequenceEqual(sources.SelectMany(s => s).OrderBy(i => i)));
}

public static IEnumerable<int> GenerateRandomCollection(Random rand, int minLength, int maxLength, int min = 0, int max = int.MaxValue)
{
    return Enumerable.Repeat(0, rand.Next(minLength, maxLength)).Select(i => rand.Next(min, max));
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X