如何先按值对 std::map 排序,然后按键排序?

Posted

技术标签:

【中文标题】如何先按值对 std::map 排序,然后按键排序?【英文标题】:How can I sort a std::map first by value, then by key? 【发布时间】:2013-11-19 10:59:46 【问题描述】:

我需要按值对std::map 进行排序,然后按键。该地图包含如下数据:

  1  realistically
  8         really
  4         reason
  3     reasonable
  1     reasonably
  1     reassemble
  1    reassembled
  2      recognize
 92         record
 48        records
  7           recs

我需要按顺序获取值,但更重要的是,在值按顺序排列之后,键需要按字母顺序排列。我该怎么做?

【问题讨论】:

你使用 std::map 来存储数据吗? 将 std::pair 放入一个列表,然后排序。 @Raxvan 是的,它存储在地图中。 @Wilbert 我知道这会将值按顺序排列,但是在对值进行排序后会保持键的字母顺序吗? 成对排序首先排序,然后是第二个项目。见here 【参考方案1】:

std::map 将按keys 对其元素进行排序。排序时它不关心values

您可以使用std::vector<std::pair<K,V>>,然后使用std::sort 后跟std::stable_sort 对其进行排序:

std::vector<std::pair<K,V>> items;

//fill items

//sort by value using std::sort
std::sort(items.begin(), items.end(), value_comparer);

//sort by key using std::stable_sort
std::stable_sort(items.begin(), items.end(), key_comparer);

第一个排序应该使用std::sort,因为它是nlog(n),然后使用std::stable_sort,在最坏的情况下是n(log(n))^2

请注意,虽然出于性能原因选择了std::sort,但正确排序需要std::stable_sort,因为您希望保留按值排序。


@gsf 在评论中指出,如果您选择一个比较器首先比较 values,并且如果它们相等,则可以使用 only std::sort,如果它们相等,则对 keys 进行排序。

auto cmp = [](std::pair<K,V> const & a, std::pair<K,V> const & b) 
 
     return a.second != b.second?  a.second < b.second : a.first < b.first;
;
std::sort(items.begin(), items.end(), cmp);

这应该是有效的。

但是等等,有一个更好的方法:存储 std::pair&lt;V,K&gt; 而不是 std::pair&lt;K,V&gt; 然后你就不需要任何比较器了 - std::pair 的标准比较器就足够了,因为它比较 first (即V)首先是second,即K

std::vector<std::pair<V,K>> items;
//...
std::sort(items.begin(), items.end());

这应该很好用。

【讨论】:

如果我像现在这样按字母顺序添加到向量中,排序的效率会受到影响吗? 哦,我明白了,不会是因为您首先按价值排序吗? @TrevorHutto:如果其中一个(keyvalue)已排序,那么您只需要stable_sort 对另一个未排序的排序。 我认为我不能在这种情况下应用它。你可能会以不同的方式告诉我。但是我有添加到地图中的函数,但是如果地图中已经包含它,它会将值增加一,当我按频率查找并尝试对字符串执行某些操作时,我该怎么做? 我得到了它与您答案中的最后一个解决方案一起工作,就像一个魅力。谢谢。【参考方案2】:

std::map 已经使用您定义的谓词或std::less 对值进行排序,如果您不提供的话。 std::set 还将按照定义比较器的顺序存储项目。但是 set 和 map 都不允许您拥有多个键。如果您想单独使用您的数据结构来完成此操作,我建议您定义一个std::map&lt;int,std::set&lt;string&gt;。您还应该意识到 std::less for string 将按字典顺序而不是按字母顺序排序。

【讨论】:

【参考方案3】:

编辑:其他两个答案说得很好。我假设您想将它们排列成其他结构,或者打印出来。

“最佳”可以表示许多不同的东西。您的意思是“最简单”、“最快”、“最高效”、“最少代码”、“最易读”吗?

最明显的方法是循环两次。在第一遍中,对值进行排序:

if(current_value > examined_value)

    current_value = examined_value
    (and then swap them, however you like)

然后在第二遍时,按字母顺序排列单词,但前提是它们的值匹配。

if(current_value == examined_value)

    (alphabetize the two)

严格来说,这是一种“冒泡排序”,速度很慢,因为每次进行交换时,都必须重新开始。当您通过整个列表而不进行任何交换时,就完成了一次“通过”。

还有其他排序算法,但原理是一样的:按值排序,然后按字母排序。

【讨论】:

【参考方案4】:

您可以使用std::set 代替std::map

您可以在std::pair 中存储键和值,容器的类型将如下所示:

std::set< std::pair<int, std::string> > items;

std::set 将按原始键和存储在std::map 中的值对其值进行排序。

【讨论】:

这里不是这样:输入是地图。这可能是由于各种原因,例如,值是数据库中出现的字符串等。我们不能在这种情况下使用 set,因为 set::insert() 将处理 ("foo", 1) & ("foo", 2 ) 不同的项目。【参考方案5】:

正如 Nawaz 的 answer 中所解释的,您无法根据需要自行对地图进行排序,因为 std::map 仅根据键对其元素进行排序。因此,您需要一个不同的容器,但如果您必须坚持使用您的地图,那么您仍然可以(临时)将其内容复制到另一个数据结构中。

我认为,最好的解决方案是使用 std::set 存储翻转的键值对,如 ks1322 的 answer 中所示。 std::set 是默认排序的,order of the pairs 正是你需要的:

3) 如果lhs.first&lt;rhs.first,则返回true。否则,如果rhs.first&lt;lhs.first,则返回false。否则,如果lhs.second&lt;rhs.second,则返回true。否则,返回false

这样您就不需要额外的排序步骤,并且生成的代码很短:

std::map<std::string, int> m;  // Your original map.
m["realistically"] = 1;
m["really"]        = 8;
m["reason"]        = 4;
m["reasonable"]    = 3;
m["reasonably"]    = 1;
m["reassemble"]    = 1;
m["reassembled"]   = 1;
m["recognize"]     = 2;
m["record"]        = 92;
m["records"]       = 48;
m["recs"]          = 7;

std::set<std::pair<int, std::string>> s;  // The new (temporary) container.

for (auto const &kv : m)
    s.emplace(kv.second, kv.first);  // Flip the pairs.

for (auto const &vk : s)
    std::cout << std::setw(3) << vk.first << std::setw(15) << vk.second << std::endl;

输出:

  1  realistically
  1     reasonably
  1     reassemble
  1    reassembled
  2      recognize
  3     reasonable
  4         reason
  7           recs
  8         really
 48        records
 92         record

Code on Ideone

注意:由于C++17,您可以使用range-based for loops 和structured bindings 来迭代地图。 结果,用于复制地图的代码变得更短且更易读:

for (auto const &[k, v] : m)
    s.emplace(v, k);  // Flip the pairs.

【讨论】:

以上是关于如何先按值对 std::map 排序,然后按键排序?的主要内容,如果未能解决你的问题,请参考以下文章

按值然后键对字典进行排序

C++ std::map sort 如何按值排序 自定义比较函数 比较对象某个字段

如何按值对散列进行排序

在Java中按键或值对地图进行排序[重复]

c++ map基础知识、按键排序、按值排序

HashMap按键排序和按值排序