【问题标题】:Keep the duplicated values only - Vectors C++仅保留重复的值 - Vectors C++
【发布时间】:2023-03-22 06:46:01
【问题描述】:

假设我有一个包含以下元素的向量 {1, 1, 2, 3, 3, 4} 我想用 c++ 代码编写一个程序来删除唯一值并只保留重复的一次。所以最终结果会是这样的 {1,3}。

到目前为止,这就是我所做的,但这需要很多时间, 有什么办法可以提高效率,

vector <int> g1 = {1,1,2,3,3,4}
vector <int> g2;

for(int i = 0; i < g1.size(); i++)
{
  if(count(g1.begin(), g1.end(), g1[i]) > 1)
    g2.push_back(g1[i]);

}

v.erase(std::unique(g2.begin(), g2.end()), g2.end());

for(int i = 0; i < g2.size(); i++)
{
  cout << g2[i];
}

【问题讨论】:

  • 输入向量会一直排序吗?如果没有,您是否只想检查彼此相邻的重复项,或者向量中的任何位置?
  • @BoBTFish 谢谢你的回复,不会排序,不会重复的不会相邻

标签: c++ vector


【解决方案1】:

我的方法是创建一个&lt;algorithm&gt; 样式的模板,并使用unordered_map 进行计数。这意味着您只需遍历输入列表一次,时间复杂度为O(n)。它确实使用了O(n) 额外的内存,而且对缓存不是特别友好。这也假设输入中的类型是可散列的。

#include <algorithm>
#include <iostream>
#include <iterator>
#include <unordered_map>

template <typename InputIt, typename OutputIt>
OutputIt copy_duplicates(
        InputIt  first,
        InputIt  last,
        OutputIt d_first)
{
    std::unordered_map<typename std::iterator_traits<InputIt>::value_type,
                       std::size_t> seen;
    for ( ; first != last; ++first) {
        if ( 2 == ++seen[*first] ) {
            // only output on the second time of seeing a value
            *d_first = *first;
            ++d_first;
        }
    }
    return d_first;
}

int main()
{
    int i[] = {1, 2, 3, 1, 1, 3, 5}; // print 1, 3,
    //                  ^     ^
    copy_duplicates(std::begin(i), std::end(i),
                    std::ostream_iterator<int>(std::cout, ", "));
}

这可以输出到任何类型的迭代器。 There are special iterators you can use that when written to will insert the value into a container.

【讨论】:

  • 非常感谢,效果很好!如果想要返回带有值的向量而不是打印它们,你能告诉我该怎么做吗?
  • @MohamedAbdelAziz - 现在听起来您希望有人为您做作业。你应该自己想办法。这并不难。 :-)
  • @MohamedAbdelAziz 我没有给出代码,但我在最后给出了强烈的提示!
  • 我写了一个版本,它对这里所有版本中最快的版本进行了基准测试。但它是 O(n log n)。我想知道 n 有多大才能淹没所有其他东西。
  • 我没有找到交叉点。事实上,这个算法有一些东西使它在非常大的 n 上表现出 n log n 性能:pastebin.com/EWNTUkdf
【解决方案2】:

这是一种比unordered_map 答案更缓存友好的方法,但它是 O(n log n) 而不是 O(n),尽管它不使用任何额外的内存并且不分配。此外,尽管缓存友好,但总体乘数可能更高。

#include <vector>
#include <algorithm>

void only_distinct_duplicates(::std::vector<int> &v)
{
    ::std::sort(v.begin(), v.end());
    auto output = v.begin();
    auto test = v.begin();
    auto run_start = v.begin();
    auto const end = v.end();
    for (auto test = v.begin(); test != end; ++test) {
       if (*test == *run_start) {
           if ((test - run_start) == 1) {
              *output = *run_start;
              ++output;
           }
       } else {
           run_start = test;
       }
    }
    v.erase(output, end);
}

我已经对此进行了测试,并且可以正常工作。如果您想要一个适用于 vector 可以存储的任何类型的通用版本:

template <typename T>
void only_distinct_duplicates(::std::vector<T> &v)
{
    ::std::sort(v.begin(), v.end());
    auto output = v.begin();
    auto test = v.begin();
    auto run_start = v.begin();
    auto const end = v.end();
    for (auto test = v.begin(); test != end; ++test) {
       if (*test != *run_start) {
           if ((test - run_start) > 1) {
              ::std::swap(*output, *run_start);
              ++output;
           }
           run_start = test;
       }
    }
    if ((end - run_start) > 1) {
        ::std::swap(*output, *run_start);
        ++output;
    }
    v.erase(output, end);
}

【讨论】:

  • 对于较小的 n,这实际上比接受的答案快 LOT。在某些时候,接受的答案应该更快。但是对于 n
  • 我认为接受的答案永远不会变得更快,因为由于哈希映射在内存中的布局方式,它将开始表现出O(N^2) 行为。顺便说一句,我稍微修改了您的实现,以便通过循环的第一次迭代已经做了有用的工作。性能方面是一样的,但也许更简单?见:godbolt.org/z/w5keYB
  • @TonvandenHeuvel - 是的,你的速度可能只会稍微快一点,而且只有在非常小的列表上才会引人注目。但是,我同意这可能更容易理解。大多数时候,我需要更好地使用&lt;algorithm&gt; 而不是原始循环。您应该使用::std::move(*first),以确保它仍然适用于可移动但不可复制的事物的向量。
【解决方案3】:

假设输入向量未排序,以下将起作用,并被推广以支持任何元素类型为 T 的向量。它将比目前提出的其他解决方案更有效。

#include <algorithm>
#include <iostream>
#include <vector>

template<typename T>
void erase_unique_and_duplicates(std::vector<T>& v)
{
  auto first{v.begin()};
  std::sort(first, v.end());
  while (first != v.end()) {
    auto last{std::find_if(first, v.end(), [&](int i) { return i != *first; })};
    if (last - first > 1) {
      first = v.erase(first + 1, last);
    }
    else {
      first = v.erase(first);
    }
  }
}

int main(int argc, char** argv)
{
  std::vector<int> v{1, 2, 3, 4, 5, 2, 3, 4};
  erase_unique_and_duplicates(v);

  // The following will print '2 3 4'.
  for (int i : v) {
    std::cout << i << ' ';
  }
  std::cout << '\n';

  return 0;
}

【讨论】:

  • 我很确定在 std::vector 中间删除某些内容效率不高。
  • 这将比unordered_map 方法更有效。这里没有内存分配。我将对此进行基准测试,现在我很好奇:)
  • @TonvandenHeuvel 以什么衡量标准?我的时间复杂度较低,但分配和缓存效果将是不可预测的。我认为这在很大程度上取决于输入集中重复的大小和数量。虽然这是一个很好的方法,但问题中没有足够的内容来说明对于提问者来说哪个是更好的解决方案。
  • @BoBTFish,您的界面禁止有效使用,因为它总是会复制。在最坏的情况下,它必须分配与输入向量一样多的内存。在空向量上使用带有back_inserter 的幼稚方法将不得不重新分配多次最坏的情况。
  • 参见:quick-bench.com/6XR8-tiDIqezQK0U4QufMdsOgdo 基准测试是不公平的,因为我必须在unordered_map 方法中清除输出向量。为了弥补这一点,我在热循环中分配了v:P
【解决方案4】:

我有 2 项改进:

  • 您可以将您的 count 更改为从 g1.begin() + i 开始,之前的所有内容都由之前的循环迭代处理。

  • 您可以将 if 更改为 == 2 而不是 &gt; 1,因此它只会添加一次数字,与出现次数无关。如果一个数字在向量中出现 5 次,则前 3 个将被忽略,第 4 个将使其进入新向量,第 5 个将再次被忽略。所以你可以删除重复的erase

例子:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    vector <int> g1 = {1,1,2,3,3,1,4};
    vector <int> g2;

    for(int i = 0; i < g1.size(); i++)
    {
      if(count(g1.begin() + i, g1.end(), g1[i]) == 2)
        g2.push_back(g1[i]);
    }

    for(int i = 0; i < g2.size(); i++)
    {
      cout << g2[i] << " ";
    }
    cout << endl;
    return 0;
}

【讨论】:

    【解决方案5】:

    我将从 Python 中借用一个非常适合此类操作的主体 -

    您可以使用字典,其中字典键是向量中的项目,字典值是计数(从 1 开始,每次遇到字典中已经存在的值时加一)。

    然后,创建一个新向量(或清除原始向量),其中仅包含大于 1 的字典键。

    在 google 中查找 - std::map

    希望这会有所帮助。

    【讨论】:

      【解决方案6】:

      一般来说,该任务的复杂度约为 O(n*n),这就是它看起来很慢的原因。它必须是一个向量吗?那是限制吗?必须订购吗?如果没有,最好将值实际存储为std::map,这样在填充时会消除双精度,或者如果双精度的存在很重要,则存储为std::multimap

      【讨论】:

        猜你喜欢
        • 2017-12-29
        • 2023-01-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-05
        • 2020-12-03
        • 2016-10-15
        • 2015-10-26
        相关资源
        最近更新 更多