Une fusion bidirectionnelle est largement étudiée dans le cadre de l'algorithme Mergesort . Mais je suis intéressé par la meilleure façon de réaliser une fusion N-way?
Disons que j'ai des fichiers N
qui ont trié 1 million d'entiers chacun . Je dois les fusionner en un seul fichier qui contiendra ces 100 millions d'entiers triés.
N'oubliez pas que le cas d'utilisation de ce problème est en réalité un tri externe basé sur le disque. Par conséquent, dans des scénarios réels, la mémoire serait également limitée. Ainsi, une approche naïve consistant à fusionner deux fichiers à la fois (99 fois) ne fonctionnera pas. Disons que nous n’avons qu’une petite fenêtre glissante de mémoire disponible pour chaque tableau.
Je ne suis pas sûr s'il existe déjà une solution normalisée à cette fusion N-way. (Googler ne m'a pas dit grand chose).
Mais si vous savez si un bon algorithme de fusion n-way, publiez algo/link.
Complexité temporelle: Si nous augmentons considérablement le nombre de fichiers (N
) à fusionner, en quoi cela affecterait-il la complexité temporelle de votre algorithme?
Merci pour vos réponses.
On ne m'a pas posé cette question nulle part, mais j'estimais que cela pourrait être une question d'entrevue intéressante. Par conséquent étiqueté.
Que diriez-vous de l'idée suivante:
L'ajout d'éléments à une file d'attente prioritaire pouvant être effectué en temps logarithmique, l'élément 2 est O (N × log N) . Puisque (presque toutes) les itérations de la boucle while ajoute un élément, la totalité de la boucle while est O (M × log N) oùMest 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 l’algorithme tout entier devrait donc être O (M × log N) .
Recherchez "Polyphase merge", consultez les classiques - Donald Knuth & E.H.Friend.
Vous voudrez peut-être aussi jeter un oeil à la proposition de Smart Block Merging de Seyedafsari & Hasanzadeh, qui, comme les suggestions précédentes, utilise des files d'attente prioritaires.
Une autre raison intéressante est Algorithme de fusion en place de Kim & Kutzner.
Je recommande également cet article de Vitter: Algorithmes de mémoire externe et structures de données: traitement de données volumineuses .
Une idée simple est de conserver une file d'attente prioritaire des plages à fusionner, stockée de manière à ce que la plage contenant le premier élément le plus petit soit retirée en premier de la file d'attente. Vous pouvez ensuite faire une fusion N-way comme suit:
La rectitude de cet algorithme est essentiellement une généralisation de la preuve du bon fonctionnement d'une fusion bidirectionnelle - si vous ajoutez toujours le plus petit élément d'une plage quelconque et que toutes les plages sont triées, vous obtenez la séquence dans son ensemble.
La complexité d'exécution de cet algorithme peut être trouvée comme suit. Soit M le nombre total d’éléments de toutes les séquences. Si nous utilisons un tas binaire, alors nous faisons au maximum O(M) insertions et O(M) suppressions de la file d'attente prioritaire, car pour chaque élément écrit dans la séquence de sortie, il existe une file d'attente à extraire la plus petite séquence, suivie d’une mise en file d’attente pour remettre le reste de la séquence dans la file. Chacune de ces étapes nécessite des opérations O (Ig N), car l’insertion ou la suppression d’un tas binaire contenant N éléments prend 0 à 1 heure. Cela donne un temps d'exécution net 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 cela encore plus rapidement, 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) temps système pour le tas binaire. Si nous mettons en œuvre le tas binaire en stockant des pointeurs sur les séquences plutôt que sur les séquences elles-mêmes, cela ne devrait pas poser trop de problème, sauf si vous avez un nombre vraiment ridicule de séquences à fusionner. Dans ce cas, fusionnez-les simplement dans des groupes qui entrent dans la mémoire, puis fusionnez tous les résultats.
J'espère que cela t'aides!
Une approche simple de la fusion de k tableaux triés (chacun de longueur n) nécessite O (nk ^ 2) heure et non O(nk). Comme lorsque vous fusionnez les deux premiers tableaux, cela prend 2n fois, puis lorsque vous fusionnez le troisième avec la sortie, cela prend 3n fois, car nous fusionnons maintenant deux tableaux de longueur 2n et n. Maintenant, lorsque nous fusionnons cette sortie avec la quatrième, cette fusion nécessite 4n fois. Ainsi, la dernière fusion (lorsque nous ajoutons le kème tableau à notre tableau déjà trié) nécessite k * n fois. Ainsi, le temps total requis est de 2n + 3n + 4n. + ... k * n qui est O (nk ^ 2).
Il semble que nous puissions le faire à l’époque O(kn), mais ce n’est pas le cas, car la taille de notre tableau que nous fusionnons augmente.
.__ Bien que nous puissions atteindre un meilleur lien en utilisant diviser pour régner. Je travaille toujours sur cela et post une solution si j'en trouve une.
Here is my implementation using MinHeap...
package merging;
import Java.io.BufferedReader;
import Java.io.BufferedWriter;
import Java.io.File;
import Java.io.FileReader;
import Java.io.FileWriter;
import Java.io.IOException;
import Java.io.PrintWriter;
public class N_Way_Merge {
int No_of_files=0;
String[] listString;
int[] listIndex;
PrintWriter pw;
private String fileDir = "D:\\XMLParsing_Files\\Extracted_Data";
private File[] fileList;
private BufferedReader[] readers;
public static void main(String[] args) throws IOException {
N_Way_Merge nwm=new N_Way_Merge();
long start= System.currentTimeMillis();
try {
nwm.createFileList();
nwm.createReaders();
nwm.createMinHeap();
}
finally {
nwm.pw.flush();
nwm.pw.close();
for (BufferedReader readers : nwm.readers) {
readers.close();
}
}
long end = System.currentTimeMillis();
System.out.println("Files merged into a single file.\nTime taken: "+((end-start)/1000)+"secs");
}
public void createFileList() throws IOException {
//creates a list of sorted files present in a particular directory
File folder = new File(fileDir);
fileList = folder.listFiles();
No_of_files=fileList.length;
assign();
System.out.println("No. of files - "+ No_of_files);
}
public void assign() throws IOException
{
listString = new String[No_of_files];
listIndex = new int[No_of_files];
pw = new PrintWriter(new BufferedWriter(new FileWriter("D:\\XMLParsing_Files\\Final.txt", true)));
}
public void createReaders() throws IOException {
//creates array of BufferedReaders to read the files
readers = new BufferedReader[No_of_files];
for(int i=0;i<No_of_files;++i)
{
readers[i]=new BufferedReader(new FileReader(fileList[i]));
}
}
public void createMinHeap() throws IOException {
for(int i=0;i<No_of_files;i++)
{
listString[i]=readers[i].readLine();
listIndex[i]=i;
}
WriteToFile(listString,listIndex);
}
public void WriteToFile(String[] listString,int[] listIndex) throws IOException{
BuildHeap_forFirstTime(listString, listIndex);
while(!(listString[0].equals("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz")))
{
pw.println(listString[0]);
listString[0]=readers[listIndex[0]].readLine();
MinHeapify(listString,listIndex,0);
}
}
public void BuildHeap_forFirstTime(String[] listString,int[] listIndex){
for(int i=(No_of_files/2)-1;i>=0;--i)
MinHeapify(listString,listIndex,i);
}
public void MinHeapify(String[] listString,int[] listIndex,int index){
int left=index*2 + 1;
int right=left + 1;
int smallest=index;
int HeapSize=No_of_files;
if(left <= HeapSize-1 && listString[left]!=null && (listString[left].compareTo(listString[index])) < 0)
smallest = left;
if(right <= HeapSize-1 && listString[right]!=null && (listString[right].compareTo(listString[smallest])) < 0)
smallest=right;
if(smallest!=index)
{
String temp=listString[index];
listString[index]=listString[smallest];
listString[smallest]=temp;
listIndex[smallest]^=listIndex[index];
listIndex[index]^=listIndex[smallest];
listIndex[smallest]^=listIndex[index];
MinHeapify(listString,listIndex,smallest);
}
}
}
Voir http://en.wikipedia.org/wiki/Sorting_externe . Voici mon point de vue sur la fusion k-way basée sur le tas, en utilisant une lecture en mémoire tampon des sources pour émuler la réduction d'E/S:
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 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));
}
J'ai écrit ce code de style STL qui fusionne N-way et j'ai pensé le poster ici pour empêcher les autres de réinventer la roue. :)
Attention: ce n'est que légèrement testé. Testez avant utilisation. :)
Vous pouvez l'utiliser comme ceci:
#include <vector>
int main()
{
std::vector<std::vector<int> > v;
std::vector<std::vector<int>::iterator> vout;
std::vector<int> v1;
std::vector<int> v2;
v1.Push_back(1);
v1.Push_back(2);
v1.Push_back(3);
v2.Push_back(0);
v2.Push_back(1);
v2.Push_back(2);
v.Push_back(v1);
v.Push_back(v2);
multiway_merge(v.begin(), v.end(), std::back_inserter(vout), false);
}
Cela permet également d'utiliser des paires d'itérateurs au lieu des conteneurs eux-mêmes.
Si vous utilisez Boost.Range, vous pouvez supprimer une partie du code standard.
Le code:
#include <algorithm>
#include <functional> // std::less
#include <iterator>
#include <queue> // std::priority_queue
#include <utility> // std::pair
#include <vector>
template<class OutIt>
struct multiway_merge_value_insert_iterator : public std::iterator<
std::output_iterator_tag, OutIt, ptrdiff_t
>
{
OutIt it;
multiway_merge_value_insert_iterator(OutIt const it = OutIt())
: it(it) { }
multiway_merge_value_insert_iterator &operator++(int)
{ return *this; }
multiway_merge_value_insert_iterator &operator++()
{ return *this; }
multiway_merge_value_insert_iterator &operator *()
{ return *this; }
template<class It>
multiway_merge_value_insert_iterator &operator =(It const i)
{
*this->it = *i;
++this->it;
return *this;
}
};
template<class OutIt>
multiway_merge_value_insert_iterator<OutIt>
multiway_merge_value_inserter(OutIt const it)
{ return multiway_merge_value_insert_iterator<OutIt>(it); };
template<class Less>
struct multiway_merge_value_less : private Less
{
multiway_merge_value_less(Less const &less) : Less(less) { }
template<class It1, class It2>
bool operator()(
std::pair<It1, It1> const &b /* inverted */,
std::pair<It2, It2> const &a) const
{
return b.first != b.second && (
a.first == a.second ||
this->Less::operator()(*a.first, *b.first));
}
};
struct multiway_merge_default_less
{
template<class T>
bool operator()(T const &a, T const &b) const
{ return std::less<T>()(a, b); }
};
template<class R>
struct multiway_merge_range_iterator
{ typedef typename R::iterator type; };
template<class R>
struct multiway_merge_range_iterator<R const>
{ typedef typename R::const_iterator type; };
template<class It>
struct multiway_merge_range_iterator<std::pair<It, It> >
{ typedef It type; };
template<class R>
typename R::iterator multiway_merge_range_begin(R &r)
{ return r.begin(); }
template<class R>
typename R::iterator multiway_merge_range_end(R &r)
{ return r.end(); }
template<class R>
typename R::const_iterator multiway_merge_range_begin(R const &r)
{ return r.begin(); }
template<class R>
typename R::const_iterator multiway_merge_range_end(R const &r)
{ return r.end(); }
template<class It>
It multiway_merge_range_begin(std::pair<It, It> const &r)
{ return r.first; }
template<class It>
It multiway_merge_range_end(std::pair<It, It> const &r)
{ return r.second; }
template<class It, class OutIt, class Less, class PQ>
OutIt multiway_merge(
It begin, It const end, OutIt out, Less const &less,
PQ &pq, bool const distinct = false)
{
while (begin != end)
{
pq.Push(typename PQ::value_type(
multiway_merge_range_begin(*begin),
multiway_merge_range_end(*begin)));
++begin;
}
while (!pq.empty())
{
typename PQ::value_type top = pq.top();
pq.pop();
if (top.first != top.second)
{
while (!pq.empty() && pq.top().first == pq.top().second)
{ pq.pop(); }
if (!distinct ||
pq.empty() ||
less(*pq.top().first, *top.first) ||
less(*top.first, *pq.top().first))
{
*out = top.first;
++out;
}
++top.first;
pq.Push(top);
}
}
return out;
}
template<class It, class OutIt, class Less>
OutIt multiway_merge(
It const begin, It const end, OutIt out, Less const &less,
bool const distinct = false)
{
typedef typename multiway_merge_range_iterator<
typename std::iterator_traits<It>::value_type
>::type SubIt;
if (std::distance(begin, end) < 16)
{
typedef std::vector<std::pair<SubIt, SubIt> > Remaining;
Remaining remaining;
remaining.reserve(
static_cast<size_t>(std::distance(begin, end)));
for (It i = begin; i != end; ++i)
{
if (multiway_merge_range_begin(*i) !=
multiway_merge_range_end(*i))
{
remaining.Push_back(std::make_pair(
multiway_merge_range_begin(*i),
multiway_merge_range_end(*i)));
}
}
while (!remaining.empty())
{
typename Remaining::iterator smallest =
remaining.begin();
for (typename Remaining::iterator
i = remaining.begin();
i != remaining.end();
)
{
if (less(*i->first, *smallest->first))
{
smallest = i;
++i;
}
else if (distinct && i != smallest &&
!less(
*smallest->first,
*i->first))
{
i = remaining.erase(i);
}
else { ++i; }
}
*out = smallest->first;
++out;
++smallest->first;
if (smallest->first == smallest->second)
{ smallest = remaining.erase(smallest); }
}
return out;
}
else
{
std::priority_queue<
std::pair<SubIt, SubIt>,
std::vector<std::pair<SubIt, SubIt> >,
multiway_merge_value_less<Less>
> q((multiway_merge_value_less<Less>(less)));
return multiway_merge(begin, end, out, less, q, distinct);
}
}
template<class It, class OutIt>
OutIt multiway_merge(
It const begin, It const end, OutIt const out,
bool const distinct = false)
{
return multiway_merge(
begin, end, out,
multiway_merge_default_less(), distinct);
}
Implémentation Java de l'algorithme de tas min pour la fusion de k tableaux triés
public class MergeKSorted {
/**
* helper object to store min value of each array in a priority queue,
* the kth array and the index into kth array
*
*/
static class PQNode implements Comparable<PQNode>{
int value;
int kth = 0;
int indexKth = 0;
public PQNode(int value, int kth, int indexKth) {
this.value = value;
this.kth = kth;
this.indexKth = indexKth;
}
@Override
public int compareTo(PQNode o) {
if(o != null) {
return Integer.valueOf(value).compareTo(Integer.valueOf(o.value));
}
else return 0;
}
@Override
public String toString() {
return value+" "+kth+" "+indexKth;
}
}
public static void mergeKSorted(int[][] sortedArrays) {
int k = sortedArrays.length;
int resultCtr = 0;
int totalSize = 0;
PriorityQueue<PQNode> pq = new PriorityQueue<>();
for(int i=0; i<k; i++) {
int[] kthArray = sortedArrays[i];
totalSize+=kthArray.length;
if(kthArray.length > 0) {
PQNode temp = new PQNode(kthArray[0], i, 0);
pq.add(temp);
}
}
int[] result = new int[totalSize];
while(!pq.isEmpty()) {
PQNode temp = pq.poll();
int[] kthArray = sortedArrays[temp.kth];
result[resultCtr] = temp.value;
resultCtr++;
temp.indexKth++;
if(temp.indexKth < kthArray.length) {
temp = new PQNode(kthArray[temp.indexKth], temp.kth, temp.indexKth);
pq.add(temp);
}
}
print(result);
}
public static void print(int[] a) {
StringBuilder sb = new StringBuilder();
for(int v : a) {
sb.append(v).append(" ");
}
System.out.println(sb);
}
public static void main(String[] args) {
int[][] sortedA = {
{3,4,6,9},
{4,6,8,9,12},
{3,4,9},
{1,4,9}
};
mergeKSorted(sortedA);
}
}