quels sont les algorithmes rapides pour trouver les éléments en double dans une collection et de les regrouper?
Dire que vous avez une collection d'éléments, comment pouvez-vous choisir ceux avec les doublons et de les mettre dans chaque groupe avec au moins le montant de la comparaison? de préférence en C++, mais l'algorithme est plus important que la langue.
Par Exemple
compte tenu de {E1,E2,E3,E4,E4,E2,E6,E4,E3}, je souhaite extraire de {E2,E2}, {E3,E3}, {E4,E4,E4}.
quelle structure de données et algorithme de vous choisir? Veuillez également inclure le coût de la création de la structure de données, dites, si c'est un pré-triés un comme std::multimap
Mises à jour
Pour rendre les choses plus claires comme le suggère. il y a une contrainte: les éléments doivent être comparés par eux-mêmes pour être certains qu'ils sont des doublons.
Donc hachages ne s'applique pas, puisque la quasi-ils passer à la comparaison à partir d'éléments lourds(par exemple, des blocs de données) à la lumière des éléments(des entiers), et de réduire certains de comparaison, mais pas les faire disparaître, et à la fin, nous sommes de retour à notre problème, lorsque vous êtes à l'intérieur d'une collision de seau.
Imaginez que vous avez un tas de potentiels de dupliquer les fichiers de l'Abg chaque, ils portent la même valeur de hachage par tous les hash-algorithme de l'homme connaît. Maintenant, vous allez repérer les doublons réels.
Non, il ne peut pas être un problème de la vie réelle(même MD5 est suffisant pour générer de hachage unique pour la vie réelle des fichiers). Mais juste faire semblant, de sorte que nous pouvons accent sur la recherche de la structure de données + algorithme qui implique moins de comparaison.
Ce que je fais est de
-
représenter dans un STL std::list structure de données(1) de son élément de suppression est moins cher que, disons, un vecteur 2) son insertion est moins cher, ne nécessitant pas de tri.)
-
pop sur un élément et de le comparer avec le reste, si un double est trouvé, il est sorti de la liste. une fois à la fin de la liste est atteinte, un groupe de duplication est trouvé, le cas échéant.
-
répéter ces 2 étapes jusqu'à ce que la liste est vide.
Il a besoin de N-1 dans le meilleur des cas, mais (N-1)! dans le pire des cas.
quelles sont les meilleures solutions?
Mon code à l'aide de la méthode expliquée ci-dessus:
//algorithm to consume the std::list container,
//supports: list<path_type>,list< pair<std::string, paths_type::const_iterater>>
template<class T>
struct consume_list
{
groups_type operator()(list<T>& l)
{
//remove spurious identicals and group the rest
//algorithm:
//1. compare the first element with the remaining elements,
// pick out all duplicated files including the first element itself.
//2. start over again with the shrinked list
// until the list contains one or zero elements.
groups_type sub_groups;
group_type one_group;
one_group.reserve(1024);
while(l.size() > 1)
{
T front(l.front());
l.pop_front();
item_predicate<T> ep(front);
list<T>::iterator it = l.begin();
list<T>::iterator it_end = l.end();
while(it != it_end)
{
if(ep.equals(*it))
{
one_group.push_back(ep.extract_path(*(it))); //single it out
it = l.erase(it);
}
else
{
it++;
}
}
//save results
if(!one_group.empty())
{
//save
one_group.push_back(ep.extract_path(front));
sub_groups.push_back(one_group);
//clear, memory allocation not freed
one_group.clear();
}
}
return sub_groups;
}
};
//type for item-item comparison within a stl container, e.g. std::list
template <class T>
struct item_predicate{};
//specialization for type path_type
template <>
struct item_predicate<path_type>
{
public:
item_predicate(const path_type& base)/*init list*/
{}
public:
bool equals(const path_type& comparee)
{
bool result;
/* time-consuming operations here*/
return result;
}
const path_type& extract_path(const path_type& p)
{
return p;
}
private:
//class members
};
};
Merci pour la réponse ci-dessous, cependant, ils semblent être induits en erreur par mon exemple que c'est sur les entiers. En fait les éléments sont de type agnostique(pas nécessairement des entiers, des chaînes ou toute autre Gousses), et de l'égalité des prédicats sont définis soi-même, c'est-à la comparaison peut être très lourd.
Peut-être que ma question devrait être: à l'aide de lequel la structure de données + algorithme implique moins de comparaisons.
À l'aide d'un pré-triés récipient comme multiset, multimap n'est pas mieux en fonction de mon test, depuis
- le tri, tout en insérant déjà fait les comparaisons,
- suivantes adjacents conclusion n'comparaison encore,
- ces données structure préfèrent moins que les opérations de l'égalité des opérations qu'ils effectuent 2 moins-que(un
Je ne vois pas comment il peut sauver des comparaisons.
une chose de plus qui est ignoré par les quelques réponses ci-dessous, j'ai besoin de différencier les dupliquer les groupes les uns des autres, de ne pas les garder dans le conteneur.
Conclusion
Après tout, la discussion, il semble y avoir 3 façons
- mon original à la méthode naïve comme expliqué ci-dessus
- Commencer avec un linéaire récipient comme
std::vector
, à les trier, puis recherchez l'égalité des plages de - commencer avec un conteneur associé comme
std::map<Type, vector<duplicates>>
, choisir les doublons lors de la configuration de conteneur associé, tel que suggéré par Charles Bailey.
J'ai codé un échantillon à tester toutes les méthodes comme affiché ci-dessous.
le nombre de doublons et lorsqu'ils sont distribués peuvent influencer le meilleur choix.
- La méthode 1 est meilleur quand ils tombent lourdement sur le devant, et est pire quand à la fin. Tri pas de changement de la distribution, mais la endian.
- Méthode 3 a le plus de rendement moyen
- La méthode 2 est jamais le meilleur choix
Merci pour tous ceux qui participent à la discussion.
une sortie avec 20 éléments de l'échantillon à partir du code ci-dessous.
Test avec [ 20 10 6 5 4 3 2 2 2 2 1
1 1 1 1 1 1 1 1 1 ]et [ 1 1 1 1 1 1 1 1 1 1 2 2 2 2 3
4 5 6 10 20 ] respectivementen utilisant std::vector -> type() ->
adjacent_find():comparaisons: ["<' = 139, '==' = 23
]comparaisons: ["<' = 38, '==' = 23 ]
en utilisant std::list -> type() -> shrink
liste:comparaisons: ["<' = 50, '==' = 43 ]
comparaisons: ["<' = 52, '==' = 43 ]
en utilisant std::list -> rétrécir liste:
comparaisons: ["<' = 0, '==' = 121 ]
comparaisons: ["<' = 0, '==' = 43 ]
en utilisant std::vector -> std::map>:
comparaisons: ["<' = 79, '==' = 0 ]
comparaisons: ["<' = 53, '==' = 0 ]
en utilisant std::vector ->
std::multiset ->
adjacent_find():comparaisons: ["<' = 79, '==' = 7 ]
comparaisons: ["<' = 53, '==' = 7 ]
Code
//compile with VC++10: cl.exe /EHsc
#include <vector>
#include <deque>
#include <list>
#include <map>
#include <set>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <boost/foreach.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/format.hpp>
using namespace std;
struct Type
{
Type(int i) : m_i(i){}
bool operator<(const Type& t) const
{
++number_less_than_comparison;
return m_i < t.m_i;
}
bool operator==(const Type& t) const
{
++number_equal_comparison;
return m_i == t.m_i;
}
public:
static void log(const string& operation)
{
cout
<< "comparison during " <<operation << ": [ "
<< "'<' = " << number_less_than_comparison
<< ", "
<< "'==' = " << number_equal_comparison
<< " ]\n";
reset();
}
int to_int() const
{
return m_i;
}
private:
static void reset()
{
number_less_than_comparison = 0;
number_equal_comparison = 0;
}
public:
static size_t number_less_than_comparison;
static size_t number_equal_comparison;
private:
int m_i;
};
size_t Type::number_less_than_comparison = 0;
size_t Type::number_equal_comparison = 0;
ostream& operator<<(ostream& os, const Type& t)
{
os << t.to_int();
return os;
}
template< class Container >
struct Test
{
void recursive_run(size_t n)
{
bool reserve_order = false;
for(size_t i = 48; i < n; ++i)
{
run(i);
}
}
void run(size_t i)
{
cout <<
boost::format("\n\nTest %1% sample elements\nusing method%2%:\n")
% i
% Description();
generate_sample(i);
sort();
locate();
generate_reverse_sample(i);
sort();
locate();
}
private:
void print_me(const string& when)
{
std::stringstream ss;
ss << when <<" = [ ";
BOOST_FOREACH(const Container::value_type& v, m_container)
{
ss << v << " ";
}
ss << "]\n";
cout << ss.str();
}
void generate_sample(size_t n)
{
m_container.clear();
for(size_t i = 1; i <= n; ++i)
{
m_container.push_back(Type(n/i));
}
print_me("init value");
Type::log("setup");
}
void generate_reverse_sample(size_t n)
{
m_container.clear();
for(size_t i = 0; i < n; ++i)
{
m_container.push_back(Type(n/(n-i)));
}
print_me("init value(reverse order)");
Type::log("setup");
}
void sort()
{
sort_it();
Type::log("sort");
print_me("after sort");
}
void locate()
{
locate_duplicates();
Type::log("locate duplicate");
}
protected:
virtual string Description() = 0;
virtual void sort_it() = 0;
virtual void locate_duplicates() = 0;
protected:
Container m_container;
};
struct Vector : Test<vector<Type> >
{
string Description()
{
return "std::vector<Type> -> sort() -> adjacent_find()";
}
private:
void sort_it()
{
std::sort(m_container.begin(), m_container.end());
}
void locate_duplicates()
{
using std::adjacent_find;
typedef vector<Type>::iterator ITR;
typedef vector<Type>::value_type VALUE;
typedef boost::tuple<VALUE, ITR, ITR> TUPLE;
typedef vector<TUPLE> V_TUPLE;
V_TUPLE results;
ITR itr_begin(m_container.begin());
ITR itr_end(m_container.end());
ITR itr(m_container.begin());
ITR itr_range_begin(m_container.begin());
while(itr_begin != itr_end)
{
//find the start of one equal reange
itr = adjacent_find(
itr_begin,
itr_end,
[] (VALUE& v1, VALUE& v2)
{
return v1 == v2;
}
);
if(itr_end == itr) break; //end of container
//find the end of one equal reange
VALUE start = *itr;
while(itr != itr_end)
{
if(!(*itr == start)) break;
itr++;
}
results.push_back(TUPLE(start, itr_range_begin, itr));
//prepare for next iteration
itr_begin = itr;
}
}
};
struct List : Test<list<Type> >
{
List(bool sorted) : m_sorted(sorted){}
string Description()
{
return m_sorted ? "std::list -> sort() -> shrink list" : "std::list -> shrink list";
}
private:
void sort_it()
{
if(m_sorted) m_container.sort();////std::sort(m_container.begin(), m_container.end());
}
void locate_duplicates()
{
typedef list<Type>::value_type VALUE;
typedef list<Type>::iterator ITR;
typedef vector<VALUE> GROUP;
typedef vector<GROUP> GROUPS;
GROUPS sub_groups;
GROUP one_group;
while(m_container.size() > 1)
{
VALUE front(m_container.front());
m_container.pop_front();
ITR it = m_container.begin();
ITR it_end = m_container.end();
while(it != it_end)
{
if(front == (*it))
{
one_group.push_back(*it); //single it out
it = m_container.erase(it); //shrink list by one
}
else
{
it++;
}
}
//save results
if(!one_group.empty())
{
//save
one_group.push_back(front);
sub_groups.push_back(one_group);
//clear, memory allocation not freed
one_group.clear();
}
}
}
private:
bool m_sorted;
};
struct Map : Test<vector<Type>>
{
string Description()
{
return "std::vector -> std::map<Type, vector<Type>>" ;
}
private:
void sort_it() {}
void locate_duplicates()
{
typedef map<Type, vector<Type> > MAP;
typedef MAP::iterator ITR;
MAP local_map;
BOOST_FOREACH(const vector<Type>::value_type& v, m_container)
{
pair<ITR, bool> mit;
mit = local_map.insert(make_pair(v, vector<Type>(1, v)));
if(!mit.second) (mit.first->second).push_back(v);
}
ITR itr(local_map.begin());
while(itr != local_map.end())
{
if(itr->second.empty()) local_map.erase(itr);
itr++;
}
}
};
struct Multiset : Test<vector<Type>>
{
string Description()
{
return "std::vector -> std::multiset<Type> -> adjacent_find()" ;
}
private:
void sort_it() {}
void locate_duplicates()
{
using std::adjacent_find;
typedef set<Type> SET;
typedef SET::iterator ITR;
typedef SET::value_type VALUE;
typedef boost::tuple<VALUE, ITR, ITR> TUPLE;
typedef vector<TUPLE> V_TUPLE;
V_TUPLE results;
SET local_set;
BOOST_FOREACH(const vector<Type>::value_type& v, m_container)
{
local_set.insert(v);
}
ITR itr_begin(local_set.begin());
ITR itr_end(local_set.end());
ITR itr(local_set.begin());
ITR itr_range_begin(local_set.begin());
while(itr_begin != itr_end)
{
//find the start of one equal reange
itr = adjacent_find(
itr_begin,
itr_end,
[] (VALUE& v1, VALUE& v2)
{
return v1 == v2;
}
);
if(itr_end == itr) break; //end of container
//find the end of one equal reange
VALUE start = *itr;
while(itr != itr_end)
{
if(!(*itr == start)) break;
itr++;
}
results.push_back(TUPLE(start, itr_range_begin, itr));
//prepare for next iteration
itr_begin = itr;
}
}
};
int main()
{
size_t N = 20;
Vector().run(20);
List(true).run(20);
List(false).run(20);
Map().run(20);
Multiset().run(20);
}
- Je pense que vous avez besoin pour décrire le but de votre algorithme un peu mieux. Étant donné que l'objectif est de se retrouver avec une liste vide, ne pas jeter le tout dans l'entrée de loin la plus rapide et la plus simple solution?
- l'entrée est une collection qui contient possible de dupliquer des éléments, et les sorties sont à zéro ou plusieurs collections qui contiennent chacune des copies de la même sorte. (Je préfère ne pas utiliser le terme de "liste", parce que cela peut impliquer certains de la mise en œuvre, par exemple std::list )
- Dans ce cas, pourquoi n'est-ce pas un pré-triés conteneur (par exemple, multiset) une solution. c'est à dire post-éditer, pourquoi avez-vous insinuer que vous devez faire "adjacent à trouver". Sûrement, une fois que le conteneur est rempli, vous venez de sortie en vue, pas de comparaisons nécessaires?
- adjacent à trouver est nécessaire, car il peut y avoir plusieurs dupliquer les groupes. par exemple, dans un pré-triés conteneur comme ceci {E1, E2, E2, E4, E4, E5}, j'ai besoin d' {E2, E2} et {E4, E4}. sans adjacente à trouver, je ne sais pas où E2 et E4 commence et se termine,
- Ce qui est requis de l'interface de sortie, alors? Avez-vous besoin à la sortie d'une collection structurée de l'objet ou êtes-vous de la sortie d'une valeur de '{', ',' et '}'? Sont " E2 " et " E2 " identiques, ou simplement l'équivalent?
- l'équivalent. et pour vérifier qu'ils sont équivalents sont des opérations coûteuses qui peuvent prendre une dizaine de secondes. l'interface de sortie n'a pas d'importance, tant que l'information est extrait. Dans mon std::multimap version de l'application, j'ai juste enregistré le std::multimap::les itérateurs correspondant à la plage. Car avec std::multimap implique des comparaisons à l'insertion et à côté de trouver. Je suis passé à un non-triés conteneur std::list dans l'espoir de sauver des comparaisons. Mais pas d'amélioration significative, ni diminution. Un conteneur délimitée avec un algorithme avec moins de comparaison est ce dont j'ai besoin.
- Le format de sortie est hors de propos trop. J'ai mis "'{', ',' et '}'" seulement à expliquer les choses d'une façon que tout le monde comprenne facilement
- Je ne pense pas que le format de sortie n'est pas pertinent. J'ai juste décrit un format de sortie qui est un plat itération à travers toute la collection qui signale le changement dans le groupe par la sortie d'un élément qui n'a pas
==
la précédente. Cela ne semble pas correspondre à ce que vous voulez si vous avez besoin d'expliquer ce qui est censé arriver à ces groupes suivant. Pour le moment je pense qu'un multiset, ou d'une carte d'un élément à une liste d'autres éléments correspondants serait de répondre à vos exigences et pas besoin d'une deuxième étape de comparaison, mais que vos besoins ne sont pas suffisamment clairs pour être certain. - avec un multiset, vous devez faire la comparaison dans l'ordre de tri lors de la construction de la multiset. c'est le 1er tour de la comparaison. Et avant que le jeu est entièrement construit, c'est à dire tous les éléments sont insérés, vous ne pouvez pas identifier les plages de doublons. vous devez attendre jusqu'à ce que toutes les insertions sont faites, maintenant vous commencez à itérer l'ensemble par(d'où un second tour de comparaison) pour identifier les plages. avec une liste, aucune comparaison n'est nécessaire au moment de l'insertion, donc seulement de faire des comparaisons à l'itération. Mais je ne suis pas sûr que ce soit la dernière est la meilleure. et c'est le but de ma question.
- Je crois que je comprends votre préoccupation, donc j'ai posté une réponse. Trouver les plages dans un multiset n'impliquera n-1 comparaisons, bien que, qui, même, y compris les plus chers d'insertion) en comparaison à la suppression d'éléments équivalents, un groupe à la fois par une recherche linéaire dans une liste peut-être pas les plus coûteux pour de nombreuses entrées.
- OK, je n'ai toujours pas compris la question. S'il vous plaît pouvez-vous mettre à jour avec des exemples de code qui implémente un muet algorithme qui génère la sortie que vous avez besoin et de mettre en évidence les opérations qui sont coûteux. Ma compréhension est que vous avez un bon marché plus faible de l'ordre et un cher plus fort afin, que vous souhaitez générer des groupes qui sont équivalentes en vertu de l'affaiblissement de l'ordre, mais avec des copies en vertu de la plus forte éliminé, mais ce qui est déduit à partir d'un grand nombre de commentaires différents et il serait bon d'avoir le problème mentionné plus précisément et ont une cible de comparaison de nombres à tirer.
- Il ne vous sauvera pas les comparaisons, mais vous avez presque certainement utiliser un vecteur au lieu d'une liste. Le Cache sont douloureuses, et l'effacer/supprimer idiome prend soin de suppressions de manière efficace. Et je ne vois pas pourquoi vous auriez besoin de beaucoup d'insertions une fois que la liste est triée.
- Bailey, le code ajouté. @jalf.com, les vecteurs sont des bons à rien, parce que je veux réduire le conteneur quand je suis sur de sorte que j'ai de moins en moins de comparaison. avec les vecteurs 1) la suppression du milieu est en effiecient, 2) et pour le pire, les itérateurs sont pas non valide après la suppression dans une boucle
- Pourquoi avez-vous besoin de réduire le vecteur d'éviter les comparaisons? Il suffit d'utiliser une paire d'itérateurs pour désigner le sous-intervalle-vous besoin de comparer. Au lieu de supprimer des éléments, de les échanger à la fin, et les exclure de cette sous-intervalle.
- parce que les éléments sont aléatoires. Si y est un sous-intervalle, il arrive à être [begin(),end()).
Vous devez vous connecter pour publier un commentaire.
Vous pouvez utiliser une carte à partir d'un élément représentatif d'une liste/vecteur/deque d'autres éléments. Cela nécessite moins de comparaisons dans l'insertion dans le conteneur, et signifie que vous pouvez parcourir les groupes sans avoir à effectuer toutes les comparaisons.
Cet exemple insère toujours le premier élément représentatif dans le mappé deque de stockage de l'itération ultérieure par le groupe logiquement simple, mais si ce dédoublement se révèle être un problème, alors il serait facile de faire la
push_back
seulementif (!ins_pair.second)
.Le parcours des groupes est alors (relativement) simple et bon marché:
J'ai effectué quelques expériences pour la comparaison et de l'objet compte. Dans un essai de 100000 objets dans un ordre aléatoire formant 50000 groupes (c et la moyenne des 2 objets par groupe) la méthode ci-dessus, le coût en nombre de comparaisons et de copies:
(J'ai essayé de ramener le nombre d'exemplaires, mais seulement réussi à vraiment au détriment de comparaisons qui semblent être à la hausse du coût de l'opération dans votre scénario.)
À l'aide d'une multimap, et de la retenue de l'élément précédent dans la dernière itération de détecter le groupe de transitions coût que cela:
À l'aide d'une liste unique et éclater la face avant de l'élément et de l'exécution d'une recherche linéaire pour les autres membres du groupe coût:
Oui, c'est ~1.9 b des comparaisons par rapport à ~1.6 m pour ma meilleure méthode. Pour obtenir la liste méthode à exécuter de n'importe où à proximité d'un nombre optimal de comparaisons, il devrait être triés et cela va coûter un nombre similaire de comparaisons, comme la construction d'une mesure ordonnée conteneur en premier lieu.
Modifier
J'ai pris votre posté code et exécuté l'algorithme implicite (j'ai dû faire des hypothèses sur le code qu'il y a que quelques suppositions définitions) sur le même jeu de données de test que j'ai utilisé avant et j'ai compté:
c'est à dire exactement le même nombre de comparaisons que mon idiot de la liste de l'algorithme, mais avec plus de copies. Je pense que cela signifie que nous utilisons essentiellement des algorithmes similaires pour ce cas. Je ne vois aucune preuve d'une alternative ordre de tri, mais il semble que vous voulez une liste des groupes qui contiennent plus d'un équivalent éléments. Ceci peut être obtenu simplement dans mon
Iterate
fonction en ajoutant dansif (i->size > 1)
clause.Je ne peux toujours pas voir la preuve que la construction d'une triés conteneur comme cette carte de deques n'est pas un bon (même si c'est pas optimal) de la stratégie.
deque
ne contient que les éléments équivalents, dites-vous que vous utilisez un autre concept pour==
et<
?deque
contient un groupe d'éléments équivalents. Selon l'original de l'énoncé du problème il n'est pas nécessaire de les commander. Ils contiennent déjà uniquement d'éléments équivalents. Maintenant vous semblent être ce qui implique qu'il existe un deuxième sous-ordre qui différencie les éléments dans chaque groupe que vous souhaitez les groupes commandés par. Si oui, pouvez-vous mettre à jour la question de faire de ce plus clair?==
et<
dérivé de cet ordre). Vous devez être un peu plus clair sur ce que les deux ordres de tri sont et comment ils contribuent à votre sortie requise.Oui, vous pouvez faire beaucoup mieux.
Trier (O(n) pour de simples entiers, O(n*log n) en général), puis les doublons sont garantis pour être adjacents, ce qui rend leur recherche rapide O(n)
Utiliser une table de hachage, également en O(n). Pour chaque élément, (a) vérifier si il est déjà dans la table de hachage; le cas échéant, à un double; dans le cas contraire, le mettre dans la table de hachage.
modifier
La méthode que vous utilisez semble faire O(N^2) comparaisons:
Donc pour longueur 5 vous n'4+3+2+1=10 compare; pour 6 vous n'15, etc. (N^2)/2 - N/2 pour être exact. N*log(N) est plus petit, pour toute raisonnablement élevé de la valeur de N.
Quelle est la taille de N dans votre cas?
Autant que la réduction des collisions de hachage, le meilleur moyen est d'obtenir une meilleure fonction de hachage :-D. en Supposant que c'est pas possible, si vous pouvez faire une variante (par exemple, différents modulous), vous pourriez être en mesure de faire une étude de hachage.
1. Trier le tableau en O(n log n) au pire - mergesort/heapsort/arbre binaire de tri etc
2. Comparer les voisins et d'en tirer les matchs O(n)
Garder une table de hachage en fonction de la structure de la valeur de comptage; si votre implémentation C++ ne propose pas de
std::hash_map
(ne fait pas partie de la norme C++ si loin!-) utilisez le Boost ou de saisir une version sur le web. Un passage sur la collection (c'est à dire, O(N)) permet de faire un value->le comte de cartographie, un de plus passer au-dessus de la table de hachage (<= O(N), clairement), afin d'identifier les valeurs, avec un nombre > 1 et émettent de façon appropriée. Ensemble O(N), ce qui n'est pas le cas pour votre suggestion.Avez-vous essayé de tri? Par exemple à l'aide d'un algorithme de type rapide le tri? Si la performance est assez bonne pour vous, alors ce serait une approche facile.
Si elle est connue pour être une liste de nombres entiers, et si l'on sait qu'ils sont tous entre A et B (par exemple A=0, B=9), faire un tableau de B-Un des éléments et créer des B-conteneurs.
Dans le cas très précis (liste de plaine entiers), je vous propose de simplement compter, puisque vous ne pouvez pas distinguer les différents nombres entiers, de toute façon:
Si ils sont distinguer, créer un tableau de listes, et les ajouter à la liste correspondante.
Si ils ne sont pas des numéros, utiliser un std::map ou std::classes hash_map, le mappage de touches pour les listes de valeurs.
La plus simple est probablement juste de trier la liste, puis d'itérer sur la recherche de dup.
Si vous savez quelque chose sur les données, les algorithmes plus efficaces sont possibles.
Par exemple, si tu savais, la liste est grande, et ne contient que des entiers de 1..n, où n est assez petit, vous pouvez utiliser une paire de tableaux de booléens (ou bitmap), et de faire quelque chose comme ceci:
Maintenant, beaucoup [de] contient un tableau de valeurs qui ont été vus plus d'une fois.
La plupart des gens de mentionner hash/non ordonnée carte solutions sont de plus en O(1) l'insertion et de la requête, cependant, il pourrait être O(N) le pire des cas. En outre, vous invalidez le coût de l'objet de hachage.
Personnellement, je voudrais insérer des objets dans un arbre binaire (O(logn) d'insertion pour chaque), et de garder le compteur à chaque nœud. Cela donnerait O(nlogn) le temps de la construction et de O(n) de la traversée d'identifier tous les doublons.
Si j'ai compris correctement à la question, c'est la solution la plus simple je pense:
Temps Total d'exécution:
O(n log n)
.Vous en avez un tri passer
O(n lg n)
puis un second passage oùO(lg n)
les comparaisons sont effectuées pour chaque groupe (si cela est fait au plusn
fois, aussi céderO(n lg n)
.Noter que cela dépend de l'entrée un vecteur. Seulement accès aléatoire itérateurs ont logarithmique de la complexité lors de la seconde passe. Itérateurs bidirectionnels serait linéaire.
Ce n'est pas basé sur le hachage (comme demandé), et il conserve tous les éléments d'origine (plutôt que de simplement retourner un élément pour chaque groupe, avec un décompte de la façon dont souvent elle a eu lieu).
Bien sûr, un certain nombre de petites constante des optimisations sont possibles.
output.reserve(input.size())
sur le vecteur de sortie serait une bonne idée, pour éviter de réaffectation.input.end()
est prise beaucoup plus souvent que nécessaire, et pourrait être facilement mis en cache.Fonction de la taille du groupes sont supposés être,
equal_range
peut ne pas être l'option la plus efficace. Je suppose qu'il fait une recherche binaire pour obtenir logarithmique de la complexité, mais si chaque groupe est à seulement quelques éléments, une simple analyse linéaire aurait été plus rapide. Dans tous les cas, le tri initial domine le coût si.Juste de s'inscrire, j'ai eu le même problème lors d'une normalisation d'une triple magasin que je suis en train de travailler avec. J'ai implémenté la méthode 3, résumé par Charles Bailey, en Common Lisp à l'aide de la table de hachage fonction de Allegro Common Lisp.
La fonction "agent de l'égalité?" est utilisé pour tester si deux agents dans les TS sont les mêmes. La fonction "merge-nœuds" fusionne les nœuds sur chaque cluster. Dans le code ci-dessous, les "..." a été utilisé pour enlever la pas si important de pièces.
Depuis C++11, les tables de hachage sont fournis par le TSL avec std::unordered_map.
Donc un O(N) la solution est de mettre vos valeurs dans un
unordered_map< T, <vector<T> >
.