【问题标题】:Calculate the union of an ordered set in C++在 C++ 中计算有序集的并集
【发布时间】:2016-02-01 13:22:26
【问题描述】:

我想组合运行长度编码方案的三种变体(游程是累积的,因此是变体)。
让我们从其中两个开始:
第一个包含布尔值列表,第二个包含计数器列表。假设第一个看起来如下:(值:该值的位置):

[(true:6), (false:10), (true:14), (false:20)]
// From 1 to 6, the value is true
// From 7 to 10, the value is false
// From 11 to 14, the value is true
// From 15 to 20, the value is false

第二个看起来如下(再次(值:该值的位置)):

[(1:4), (2:8), (4:16), (0:20)]
// From 1 to 4, the value is 1
// From 5 to 8, the value is 2
// From 9 to 16, the value is 4
// From 17 to 20, the value is 0

如您所见,两种情况下的位置略有不同:

Case 1 : [6, 10, 14, 20]
Case 2 : [4, 8, 16, 20]

我想通过计算它们的联合来组合这些“位置数组”:

[4, 6, 8, 10, 14, 16, 20]

一旦我有了这个,我就会从那里推导出新的方案:

[(true:4), (true:6), (false:8), (false:10), (true:14), (false:16), (false:20)]
[(1:4), (2:6), (2:8), (4:10), (4:14), (4:16), (0:20)]

我想知道:是否有任何 C++ 标准类型/类可以包含“数组”[6、10、14、20] 和 [4、8、16、20],计算它们的并集并对其进行排序?

谢谢
多米尼克

【问题讨论】:

标签: c++ class types stl set


【解决方案1】:

您需要使用 <algorithm> 中的 std::set_union

我在这里使用std::vector<int>,但它可以是任何模板类型。

#include <iostream>
#include <array>
#include <algorithm>

int main() {
  std::vector<int> a{6, 10, 14, 20};
  std::vector<int> b{4, 8, 16, 20};
  std::vector<int> c;

  std::set_union(a.begin(), a.end(), b.begin(), b.end(), std::back_inserter(c));
  for(auto e: c) {
    std::cout << e << ' ';
  }
  std::cout << '\n';
}

Here's the ideone

如果您想只保留两个std::vectors 而不引入c,您可以简单地将b 附加到a,对数组进行排序,然后在a 上调用std::unique。在O(n)可能有一个聪明的方法来做到这一点,但这是一种天真的方法:

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

int main() {
  std::vector<int> a{6, 10, 14, 20};
  std::vector<int> b{4, 8, 16, 20};

  a.insert(a.end(), b.begin(), b.end());

  std::sort(a.begin(), a.end());
  auto last = std::unique(a.begin(), a.end());
  a.erase(last, a.end());

  for(auto e: a) {
    std::cout << e << ' ';
  }
  std::cout << '\n';
}

Here's the ideone

最后,您可以使用std::inplace_merge 代替std::sort。在最坏的情况下是O(nlogn),就像std::sort,但在最好的情况下是O(n)。性能大幅提升:

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

int main() {
  std::vector<int> a{6, 10, 14, 20};
  std::vector<int> b{4, 8, 16, 20};

  auto a_size = a.size();

  a.insert(a.end(), b.begin(), b.end());

  // merge point is where `a` and `b` meet: at the end of original `a`.    
  std::inplace_merge(a.begin(), a.begin() + a_size, a.end());

  auto last = std::unique(a.begin(), a.end());
  a.erase(last, a.end());

  for(auto e: a) {
    std::cout << e << ' ';
  }
  std::cout << '\n';
}

Here's the ideone

【讨论】:

  • 感谢您的快速回复。只是为了确定:我想计算一个向量列表(总共 4 个)的并集,启动以下内容是否安全:std::set_union(a.begin(),a.end(), b.begin(), b.end(), std::back_inserter(a));(我的意思是,使用 'a' 作为输入和输出变量?
  • 这不安全,因为迭代器将失效。我可以在一分钟内更新另一个选项。
  • 可以使用std::inplace_merge,而不是对数组进行排序。
  • @Jarod42 太漂亮了。我知道std::merge,但知道它不在位。谢谢!
【解决方案2】:

我想知道:是否有任何 C++ 标准类型/类可以包含“数组”[6、10、14、20] 和 [4、8、16、20],计算它们的并集并对其进行排序?

我猜你在问这个问题之前没有做太多研究。有一个管理有序集的类模板,称为set。如果您将两个集合的所有元素添加到一个集合中,您将拥有并集。

std::set<int> s1{6, 10, 14, 20};
std::set<int> s2{4, 8, 16, 20};

std::set<int> union = s1;
union.insert(s2.begin(), s2.end());

【讨论】:

    【解决方案3】:

    正如erip 所暗示的,有一种算法只需要您对两个向量进行一次迭代。作为先决条件,它们都必须在开始时进行排序。您可以使用该事实来始终检查哪个更小,并且只将该向量中的一个值附加到结果中。它还允许您删除重复项,因为如果您想添加一个值,那么只有当它是添加到结果向量中的最后一个值时,该值才会是重复项。

    我已经编写了一些代码;我还没有对它进行广泛的测试,所以它可能仍然有一点问题,但你去吧:

    // Assume a and b are the input vectors, and they are sorted.
    std::vector<int> result;
    
    // We know how many elements we will get at most, so prevent reallocations
    result.reserve(a.size() + b.size());
    
    auto aIt = a.cbegin();
    auto bIt = b.cbegin();
    
    // Loop until we have reached the end for both vectors
    while(aIt != a.cend() && bIt != b.cend())
    {
        // We pick the next value in a if it is smaller than the next value in b.
        // Of course we cannot do this if we are at the end of a.
        // If b has no more items, we also take the value from a.
        if(aIt != a.end() && (bIt == b.end() || *aIt < *bIt))
        {
            // Skip this value if it equals the last added value
            // (of course, for result.back() we need it to be nonempty)
            if(result.size() == 0 || *aIt != result.back())
            {
                result.push_back(*aIt);
            }
            ++aIt;
        }
        // We take the value from b if a has no more items, 
        // or if the next item in a was greater than the next item in b
        else
        {
            // If we get here, then either aIt == a.end(), in which case bIt != b.end() (see loop condition)
            // or bIt != b.end() and *aIt >= *bIt.
            // So in either case we can safely dereference bIt here.
            if(result.size() == 0 || *bIt != result.back())
            {
                result.push_back(*bIt);
            }
            ++bIt;
        }
    }
    

    它允许在样式和性能方面进行一些优化,但我认为它总体上有效。

    当然,如果您希望将结果返回到 a,您可以修改此算法以直接插入到 a,但保持这样的状态并在末尾添加 a.swap(result) 可能会更快。

    你可以在here看到它。

    【讨论】:

    • @Jarod42 很好,我交换了第一个 if 中的条件以避免这种情况,并在 else 中添加了一些 cmets 来解释为什么无法取消引用无效的 bIt .如果您发现更多错误,请随时更正。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-29
    • 1970-01-01
    • 2017-05-15
    相关资源
    最近更新 更多