web-dev-qa-db-fra.com

comment insérez-vous la valeur dans un vecteur trié?

TOUT,

Cette question est une continuation de celle-ci . Je pense que cette fonctionnalité manque à STL, mais que ce n'est que mon IMHO.

Maintenant, à la question.

Considérons le code suivant:

class Foo
{
public:
    Foo();
...........
private:
    int paramA, paramB;
    std::string name;
};

int main()
{
    std::vector<Foo> foo;
    Sorter sorter;
    sorter.paramSorter = 0;
    std::sort( foo.begin(), foo.end(), sorter );
}

struct Sorter
{
    bool operator()(const Foo &foo1, const Foo &foo2)
    {
         switch( paramSorter )
         {
             case 0:
                 return foo1.name < foo2.name;
             case 1:
                 return foo1.paramA < foo2.paramB;
             case 2:
                 return foo1. paramA > foo2.paramB;
         }
    }
private:
    int paramSorter;
}

À tout moment, le vecteur peut être re-trié ..__ La classe possède également les méthodes de lecture utilisées dans la structure du trieur.

Quel serait le moyen le plus efficace d’insérer un nouvel élément dans le vecteur?

La situation que j'ai est:

J'ai une grille (feuille de calcul), qui utilise le vecteur trié d'une classe. À tout moment, le vecteur peut être trié à nouveau et la grille affichera les données triées en conséquence.

Maintenant, je vais devoir insérer un nouvel élément dans le vecteur/grille ..__ Je peux insérer, puis trier à nouveau et ensuite afficher à nouveau la grille entière, mais cela est très inefficace, surtout pour la grande grille.

Toute aide serait appréciée.

32
Igor

La réponse simple à la question:

template< typename T >
typename std::vector<T>::iterator 
   insert_sorted( std::vector<T> & vec, T const& item )
{
    return vec.insert
        ( 
            std::upper_bound( vec.begin(), vec.end(), item ),
            item 
        );
}

Version avec un prédicat.

template< typename T, typename Pred >
typename std::vector<T>::iterator
    insert_sorted( std::vector<T> & vec, T const& item, Pred pred )
{
    return vec.insert
        ( 
           std::upper_bound( vec.begin(), vec.end(), item, pred ),
           item 
        );
}

Où Pred est un prédicat strictement ordonné sur le type T. 

Pour que cela fonctionne, le vecteur d'entrée doit déjà être trié sur ce prédicat.

La complexité de cette opération est O(log N) pour la recherche upper_bound (trouver où insérer), mais jusqu’à O(N) pour l’insertion elle-même.

Pour une meilleure complexité, vous pouvez utiliser std::set<T> s'il n'y a pas de doublons ou std::multiset<T> s'il peut y en avoir. Ceux-ci conserveront automatiquement une commande triée et vous pourrez également spécifier votre propre prédicat sur ceux-ci.

Vous pouvez faire diverses choses plus complexes, par exemple: gérer une vector et une set/multiset/sorted vector des éléments nouvellement ajoutés, puis les fusionner lorsqu'ils sont en nombre suffisant. Tout type d'itération dans votre collection devra passer par les deux collections. 

L'utilisation d'un second vecteur présente l'avantage de garder vos données compactes. Ici, vos "nouvellement ajoutés" éléments vector seront relativement petits et le temps d’insertion sera donc O(M), où M sera la taille de ce vecteur et pourrait être plus réalisable que le O(N) qui consiste à insérer chaque fois dans le grand vecteur. La fusion serait O(N+M), ce qui est meilleur que O(NM), ce serait en insérant un à la fois, donc au total, il serait O(N+M) + O(M²) d'insérer des éléments M puis de les fusionner.

Vous voudriez probablement garder le vecteur d'insertion aussi, de sorte que plus vous grandissez, plus vous ne ferez de réaffectations, vous ne faites que déplacer des éléments.

50
CashCow

Si vous devez garder le vecteur trié tout le temps, vous pouvez d’abord déterminer si utiliser std::set ou std::multiset ne simplifiera pas votre code.

Si vous avez réellement besoin d'un vecteur trié et que vous souhaitez y insérer rapidement un élément, mais que vous ne souhaitez pas imposer un critère de tri pour qu'il soit satisfait à tout moment, vous pouvez d'abord utiliser std::lower_bound() pour rechercher la position dans plage où l'élément doit être inséré en temps logarithmique, puis utilisez la fonction insert() membre de vector pour insérer l'élément à cette position.

Si les performances sont un problème, envisagez d’analyser std::list par rapport à std::vector. Pour les petits éléments, std::vector est connu pour être plus rapide en raison d'un taux d'accès au cache plus élevé, mais l'opération insert() elle-même est plus rapide en calcul sur les listes (nul besoin de déplacer des éléments).

24
Andy Prowl

Juste une note, vous pouvez aussi utiliser upper_bound en fonction de vos besoins. upper_bound assurera que les nouvelles entrées équivalentes aux autres apparaîtront au fin de leur séquence, lower_bound veillera aux nouvelles entrées équivalentes aux autres apparaîtront au début de leur séquence. Peut être utile pour certaines implémentations (peut-être que les classes peuvent partager une "position" mais pas tous leurs détails!)

Both vous assurera que le vecteur reste trié en fonction de < résultat des éléments, bien que l'insertion dans lower_bound implique le déplacement de plusieurs éléments.

Exemple:

insert 7 @ lower_bound of { 5, 7, 7, 9 } => { 5, *7*, 7, 7, 9 }
insert 7 @ upper_bound of { 5, 7, 7, 9 } => { 5, 7, 7, *7*, 9 }
8
Brian Rodriguez

Au lieu d'insérer et de trier. Vous devriez faire une recherche et ensuite insérer

Gardez le vecteur trié. (trier une fois). Quand vous devez insérer 

  1. trouvez le premier élément qui se compare le plus à celui que vous allez insérer.

  2. Faites une insertion juste avant cette position.

De cette façon, le vecteur reste trié.

Voici un exemple de comment ça se passe.

start {} empty vector

insert 1 -> find first greater returns end() = 1 -> insert at 1 -> {1}
insert 5 -> find first greater returns end() = 2 -> insert at 2 -> {1,5}
insert 3 -> find first greater returns 2 -> insert at 2 -> {1,3,5}
insert 4 -> find first greater returns 3 -> insert at 3 -> {1,3,4,5}
1
user995502

Lorsque vous souhaitez basculer entre les ordres de tri, vous pouvez utiliser plusieurs infrastructures de données d'index, que vous gardez dans l'ordre de tri (probablement une sorte d'arborescence équilibrée, comme std :: map, qui associe des clés de tri à des index vectoriels ou :: paramétrez pour stocker les pointeurs sur vos objectifs - mais avec des fonctions de comparaison différentes).

Voici une bibliothèque qui fait cela: http://www.boost.org/doc/libs/1_53_0/libs/multi_index/doc/index.html

Pour chaque modification (insertion de nouveaux éléments ou mise à jour de clés), vous devez mettre à jour toutes les structures de données d'index ou les marquer comme non valides.

Cela fonctionne s'il n'y a pas "trop" d'ordres de tri et pas "trop" de mises à jour de votre structure de données. Sinon - pas de chance, vous devez trier à chaque fois que vous souhaitez modifier l'ordre.

En d'autres termes: plus vous avez besoin d'indices (pour accélérer les opérations de recherche), plus vous avez besoin de temps pour les opérations de mise à jour. Et chaque index a besoin de mémoire, bien sûr.

Pour limiter le nombre d'index, vous pouvez utiliser un moteur de requête combinant les index de plusieurs champs pour prendre en charge des ordres de tri plus complexes sur plusieurs champs. Comme un optimiseur de requête SQL. Mais c'est peut-être exagéré ...

Exemple: Si vous avez deux champs, a et b, vous pouvez gérer 4 ordres de tri:

  1. une
  2. b
  3. d'abord a puis b
  4. d'abord b puis a

avec 2 index (3. et 4.) . Avec plus de champs, les combinaisons possibles d'ordres de tri deviennent grandes, rapides. Mais vous pouvez toujours utiliser un index qui trie "presque comme vous le souhaitez" et, au cours de la requête, triez les champs restants que vous ne pouviez pas saisir avec cet index, si nécessaire. Pour une sortie triée de l'ensemble des données, cela n'aide pas beaucoup. Mais si vous souhaitez uniquement rechercher certains éléments, la première "réduction" peut vous être très utile.

0
Sebastian