2012-01-26 44 views
8

Tengo una colección de cerca de un centenar de ordenados vector<int> 's Aunque la mayoría de los vectores tienen un pequeño número de enteros en ellos, algunos de los vectores contienen una gran (> 10K) de ellos (por lo tanto los vectores no necesariamente tienen el mismo tamaño).C++ Cómo combinar vectores ordenados en un vector ordenado/pop el elemento mínimo de todos ellos?

Lo que me gustaría hacer esencialmente iterar a través de menor a mayor número entero, que están contenidos en todos estos vectores ordenados.

Una forma de hacerlo sería fusionar todos estos vectores ordenados en un vector ordenado & simplemente iterar. Por lo tanto,

Pregunta 1: ¿Cuál es la forma más rápida de combinar vectores ordenados en un vector ordenado?

Estoy seguro de que, por otro lado, hay formas más rápidas/inteligentes de lograr esto sin combinar & reordenar todo el asunto, quizás haciendo estallar el entero más pequeño iterativamente desde esta colección de vectores ordenados; sin fusionarlos primera .. así:

Pregunta 2: ¿Cuál es la mejor manera de ayuno/a estallar el elemento mínimo de un montón de ordenados vector<int> 's?


Sobre la base de las respuestas abajo, y los comentarios a la pregunta que me he aplicado un enfoque donde hago una cola de prioridad de iteradores para los vectores ordenados. No estoy seguro de si se trata de rendimiento con eficiencia, pero parece ser muy eficiente en la memoria. Considero que la pregunta aún está abierta, ya que no estoy seguro de haber establecido la manera más rápida hasta el momento.

// compare vector pointers by integers pointed 
struct cmp_seeds { 
    bool operator() (const pair< vector<int>::iterator, vector<int>::iterator> p1, const pair< vector<int>::iterator, vector<int>::iterator> p2) const { 
     return *(p1.first) > *(p2.first);  
    } 
}; 

int pq_heapsort_trial() { 

    /* Set up the Sorted Vectors */ 
    int a1[] = { 2, 10, 100}; 
    int a2[] = { 5, 15, 90, 200}; 
    int a3[] = { 12 }; 

    vector<int> v1 (a1, a1 + sizeof(a1)/sizeof(int)); 
    vector<int> v2 (a2, a2 + sizeof(a2)/sizeof(int)); 
    vector<int> v3 (a3, a3 + sizeof(a3)/sizeof(int)); 

    vector< vector <int> * > sorted_vectors; 
    sorted_vectors.push_back(&v1); 
    sorted_vectors.push_back(&v2); 
    sorted_vectors.push_back(&v3); 
    /* the above simulates the "for" i have in my own code that gives me sorted vectors */ 

    pair< vector<int>::iterator, vector<int>::iterator> c_lead; 
    cmp_seeds mycompare; 

    priority_queue< pair< vector<int>::iterator, vector<int>::iterator>, vector<pair< vector<int>::iterator, vector<int>::iterator> >, cmp_seeds> cluster_feeder(mycompare); 


    for (vector<vector <int> *>::iterator k = sorted_vectors.begin(); k != sorted_vectors.end(); ++k) { 
     cluster_feeder.push(make_pair((*k)->begin(), (*k)->end())); 
    } 


    while (cluster_feeder.empty() != true) { 
     c_lead = cluster_feeder.top(); 
     cluster_feeder.pop(); 
     // sorted output 
     cout << *(c_lead.first) << endl; 

     c_lead.first++; 
     if (c_lead.first != c_lead.second) { 
      cluster_feeder.push(c_lead); 
     } 
    } 

    return 0; 
} 
+1

1) Si el espacio no es un problema, realice la fusión estándar de rangos ordenados de su CS101 a un nuevo vector (o simplemente piénselo un minuto y haga lo obvio) 2) Antes de ir por todas partes, asegúrese de comprender las garantías de complejidad de los contenedores estándar; modificar un 'std :: vector' es en general bastante caro. 3) ¡Deja de abjurar de los apo'strophes! –

+0

@ Kerrek-SB Gracias, corrigió el formato un poco. Estoy bastante contento simplemente fusionando los vectores en un vector más grande y ordenando; pero me pregunto si hay formas más rápidas de hacerlo. – Deniz

+0

No, no, realiza una fusión ordenada. Piénselo, hay una manera obvia de explotar el orden de los rangos de entrada para crear un rango de salida ya ordenado. –

Respuesta

4

Una opción es usar un std :: priority queue para mantener un montón de iteradores, donde los iteradores aumentan el montón dependiendo de los valores que señalan.

También podría considerar el uso de aplicaciones repetitivas de std :: inplace_merge. Esto implicaría agregar todos los datos en un gran vector y recordar los desplazamientos en los que cada bloque ordenado distinto comienza y termina, y luego pasarlos a inplace_merge. Esto probablemente sería más rápido que la solución de almacenamiento dinámico, aunque creo que fundamentalmente la complejidad es equivalente.

Actualización: Implementé el segundo algoritmo que acabo de describir. Repetidamente haciendo un mergesort en su lugar. Este código está en ideone.

Esto funciona al unir todas las listas ordenadas juntas en una larga lista. Si hubo tres listas de origen, esto significa que hay cuatro 'compensaciones', que son cuatro puntos en la lista completa entre los elementos que se ordenan. El algoritmo realizará tres de estos a la vez, fusionando las dos listas ordenadas adyacentes correspondientes en una lista ordenada, y luego recordando dos de esas tres compensaciones para usar en los nuevos conjuntos de cambios.

Esto se repite en un bucle, con pares de rangos ordenados adyacentes fusionados, hasta que solo quede un rango ordenado.

En última instancia, creo que el mejor algoritmo implicaría fusionar primero los pares más cortos de rangos adyacentes.

// http://stackoverflow.com/questions/9013485/c-how-to-merge-sorted-vectors-into-a-sorted-vector-pop-the-least-element-fro/9048857#9048857 
#include <iostream> 
#include <vector> 
#include <algorithm> 
#include <cassert> 
using namespace std; 

template<typename T, size_t N> 
vector<T> array_to_vector(T(*array)[N]) { // Yes, this works. By passing in the *address* of 
              // the array, all the type information, including the 
              // length of the array, is known at compiler. 
     vector<T> v(*array, &((*array)[N])); 
     return v; 
} 

void merge_sort_many_vectors() { 

    /* Set up the Sorted Vectors */ 
    int a1[] = { 2, 10, 100}; 
    int a2[] = { 5, 15, 90, 200}; 
    int a3[] = { 12 }; 

    vector<int> v1 = array_to_vector(&a1); 
    vector<int> v2 = array_to_vector(&a2); 
    vector<int> v3 = array_to_vector(&a3); 


    vector<int> full_vector; 
    vector<size_t> offsets; 
    offsets.push_back(0); 

    full_vector.insert(full_vector.end(), v1.begin(), v1.end()); 
    offsets.push_back(full_vector.size()); 
    full_vector.insert(full_vector.end(), v2.begin(), v2.end()); 
    offsets.push_back(full_vector.size()); 
    full_vector.insert(full_vector.end(), v3.begin(), v3.end()); 
    offsets.push_back(full_vector.size()); 

    assert(full_vector.size() == v1.size() + v2.size() + v3.size()); 

    cout << "before:\t"; 
    for(vector<int>::const_iterator v = full_vector.begin(); v != full_vector.end(); ++v) { 
      cout << ", " << *v; 
    }  
    cout << endl; 
    while(offsets.size()>2) { 
      assert(offsets.back() == full_vector.size()); 
      assert(offsets.front() == 0); 
      vector<size_t> new_offsets; 
      size_t x = 0; 
      while(x+2 < offsets.size()) { 
        // mergesort (offsets[x],offsets[x+1]) and (offsets[x+1],offsets[x+2]) 
        inplace_merge(&full_vector.at(offsets.at(x)) 
           ,&full_vector.at(offsets.at(x+1)) 
           ,&(full_vector[offsets.at(x+2)]) // this *might* be at the end 
           ); 
        // now they are sorted, we just put offsets[x] and offsets[x+2] into the new offsets. 
        // offsets[x+1] is not relevant any more 
        new_offsets.push_back(offsets.at(x)); 
        new_offsets.push_back(offsets.at(x+2)); 
        x += 2; 
      } 
      // if the number of offsets was odd, there might be a dangling offset 
      // which we must remember to include in the new_offsets 
      if(x+2==offsets.size()) { 
        new_offsets.push_back(offsets.at(x+1)); 
      } 
      // assert(new_offsets.front() == 0); 
      assert(new_offsets.back() == full_vector.size()); 
      offsets.swap(new_offsets); 

    } 
    cout << "after: \t"; 
    for(vector<int>::const_iterator v = full_vector.begin(); v != full_vector.end(); ++v) { 
      cout << ", " << *v; 
    } 
    cout << endl; 
} 

int main() { 
     merge_sort_many_vectors(); 
} 
+0

gracias Aaron, implementó la primera sugerencia y publicó el código: ¿alguna sugerencia? Si me muero por hacerlo, inplace_merge se actualizará nuevamente. – Deniz

+0

@Deniz, su algoritmo priority_queue se ve bien. He actualizado mi respuesta aquí para incluir una implementación de mi segundo algoritmo, donde los pares de rangos ordenados adyacentes se combinan de forma repetida hasta que solo quede un rango. –

+0

@ AaronMcDaid Probé el programa anterior con diferentes entradas y los resultados no estaban ordenados. Entrada: int a1 [] = {30, 50, 3, 8}; int a2 [] = {11, 14, 19, 6, 8, 30}; int a3 [] = {8, 6}; Salida: 11, 14, 19, 6, 8, 30, 30, 50, 3, 8, 6, 8 – SyncMaster

2

La primera cosa que viene a la mente es hacer una estructura de montón que contiene iteradores a cada vector, ordenados por el valor que actualmente apuntan a. (cada entrada debería contener el iterador final también, por supuesto)

El elemento actual está en la raíz del montón, y para avanzar, simplemente lo pop o aumenta su clave. (Este último se podría hacer por reventar, incrementando, a continuación, empujar)

creo que esto debe tener la complejidad asintótica O(E log M)E donde es el número total de elementos, y M es el número de vectores.

Si realmente está sacando todo de los vectores, puede hacer un montón de punteros a sus vectores, puede tratarlos también como montones, para evitar la penalización de rendimiento de borrar de la parte frontal de un vector. (O bien, puede copiar todo en deque s primero)


La fusión de todos ellos juntos mediante la fusión de pares a la vez tiene la misma complejidad asintótica si usted tiene cuidado sobre el orden. Si organiza todos los vectores en un árbol binario completo y equilibrado, a continuación, combine por pares a medida que sube el árbol, y luego cada elemento se copiará log M veces, lo que también conduce a un algoritmo O(E log M).

Para la eficacia real adicional, en lugar del árbol, se debe combinar varias veces los dos vectores más pequeños hasta que sólo tiene una izquierda.(De nuevo, poner los punteros a los vectores en un montón es el camino a seguir, pero esta vez ordenados por longitud)

(realmente, usted quiere ordenar por "costo de copiar" en lugar de longitud. Una cosa adicional para optimizar para ciertos tipos de valores)


Si tuviera que adivinar, la forma más rápida sería el uso de la segunda idea, pero con una fusión N-aria en lugar de una combinación de dos a dos, para algún n adecuada (que yo' m adivinar será una pequeña constante, o aproximadamente la raíz cuadrada del número de vectores), y realizar la fusión N-aria utilizando el primer algoritmo anterior para enumerar los contenidos de N vectores a la vez.

+0

Por supuesto, para datos especializados, es mejor que haga una ordenación de tiempo lineal; p.ej. un histograma o una clasificación de cubo o una clasificación de radix. – Hurkyl

+0

Gracias por su respuesta, soy relativamente nuevo, ¿podría proporcionar algún código de ejemplo con fines ilustrativos? (1) ¿Cómo se hace una fusión N-ary? (2) ¿Cómo "la estructura del montón que contiene los iteradores a cada vector, ordenados por el valor que actualmente apuntan a. (Cada entrada debería contener el iterador final también, por supuesto) El elemento actual está en la raíz del montón , y para avanzar, simplemente lo pop, o aumenta su clave. (Esto último se puede hacer haciendo clic, incrementando y luego presionando) "buscar en el código? – Deniz

0

He utilizado el algoritmo dado aquí e hice un pequeño resumen; convirtiendo a plantillas. Codifiqué esta versión en VS2010 y usé una función lambda en lugar del functor. No sé si esto es, en cierto sentido, "mejor" que la versión anterior, pero ¿será útil alguien?

#include <queue> 
#include <vector> 

namespace priority_queue_sort 
{ 
    using std::priority_queue; 
    using std::pair; 
    using std::make_pair; 
    using std::vector; 

    template<typename T> 
    void value_vectors(const vector< vector <T> * >& input_sorted_vectors, vector<T> &output_vector) 
    { 
     typedef vector<T>::iterator iter; 
     typedef pair<iter, iter> iter_pair; 

     static auto greater_than_lambda = [](const iter_pair& p1, const iter_pair& p2) -> bool { return *(p1.first) > *(p2.first); }; 

     priority_queue<iter_pair, std::vector<iter_pair>, decltype(greater_than_lambda) > cluster_feeder(greater_than_lambda); 

     size_t total_size(0); 

     for (auto k = input_sorted_vectors.begin(); k != input_sorted_vectors.end(); ++k) 
     { 
      cluster_feeder.push(make_pair((*k)->begin(), (*k)->end())); 
      total_size += (*k)->size(); 
     } 

     output_vector.resize(total_size); 
     total_size = 0; 
     iter_pair c_lead; 
     while (cluster_feeder.empty() != true) 
     { 
      c_lead = cluster_feeder.top(); 
      cluster_feeder.pop(); 
      output_vector[total_size++] = *(c_lead.first); 
      c_lead.first++; 
      if (c_lead.first != c_lead.second) cluster_feeder.push(c_lead); 
     } 
    } 

    template<typename U, typename V> 
    void pair_vectors(const vector< vector < pair<U, V> > * >& input_sorted_vectors, vector< pair<U, V> > &output_vector) 
    { 
     typedef vector< pair<U, V> >::iterator iter; 
     typedef pair<iter, iter> iter_pair; 

     static auto greater_than_lambda = [](const iter_pair& p1, const iter_pair& p2) -> bool { return *(p1.first) > *(p2.first); }; 

     priority_queue<iter_pair, std::vector<iter_pair>, decltype(greater_than_lambda) > cluster_feeder(greater_than_lambda); 

     size_t total_size(0); 

     for (auto k = input_sorted_vectors.begin(); k != input_sorted_vectors.end(); ++k) 
     { 
      cluster_feeder.push(make_pair((*k)->begin(), (*k)->end())); 
      total_size += (*k)->size(); 
     } 

     output_vector.resize(total_size); 
     total_size = 0; 
     iter_pair c_lead; 

     while (cluster_feeder.empty() != true) 
     { 
      c_lead = cluster_feeder.top(); 
      cluster_feeder.pop(); 
      output_vector[total_size++] = *(c_lead.first); 
      c_lead.first++; 
      if (c_lead.first != c_lead.second) cluster_feeder.push(c_lead); 
     } 
    } 
} 

El algoritmo priority_queue_sort::value_vectors ordena los vectores que contienen sólo valores; mientras que priority_queue_sort::pair_vectors ordena vectores que contienen pares de datos de acuerdo con el primer elemento de datos. Espero que alguien pueda usar esto algún día :-)

Cuestiones relacionadas