【问题标题】:Removing duplicates from an array using std::map使用 std::map 从数组中删除重复项
【发布时间】:2012-06-09 22:48:06
【问题描述】:

我在 5 分钟内直接发布了我在 collabedit 上编写的代码(包括弄清楚算法),因此即使在效率方面完全取笑我的风险我想请教各位有经验的栈溢出算法爱好者;

基本上从数组中删除重复元素。 我的方法:基本上使用 std::map 作为我的哈希表,如果没有分配值,则将重复数组中的每个元素添加到我们的新数组中。如果分配只是跳过。最后返回唯一的数组。这是我的代码,我在面试问题方面唯一要问的问题是我的解决方案可以更有效吗?

#include <iostream>
#include <vector>
#include <map>

using namespace std;

vector<int>uniqueArr(int arr[],int size){
    std::map<int,int>storedValues;
    vector<int>uniqueArr;
    for(int i=0;i<size;i++){
        if(storedValues[arr[i]]==0){
            uniqueArr.push_back(arr[i]);
            storedValues[arr[i]]=1;
        }
    }
    return uniqueArr;  
}

int main()
{   
    const int size=10;
    int arr[size]={1,2,2,4,2,5,6,5,7,1};
    vector<int>uniArr=uniqueArr(arr,size);
    cout<<"Result: ";
    for(int i=0;i<uniArr.size();i++) cout<<uniArr[i]<<" ";
    cout<<endl;
    return 0;
}

【问题讨论】:

  • 这个问题是专门要求你用C++做的,还是你自己选的?
  • @James 这是我觉得最舒服的语言。

标签: c++ performance algorithm


【解决方案1】:

首先,不需要映射,集合在概念上更正确,因为您不想存储任何值,而只想存储键。

在性能方面,使用std::unordered_set 而不是std::set 可能是一个更好的主意,因为前者是散列的,并且在最佳情况下可以为您提供 O(1) 插入和查找,而后者是二叉搜索树,只给你 O(log n) 访问权限。

vector<int> uniqueArr(int arr[], int size)
{
    std::unordered_set<int> storedValues;
    vector<int> uniqueArr;
    for(int i=0; i<size; ++i){
        if(storedValues.insert(arr[i]).second)
            uniqueArr.push_back(arr[i]);
    return uniqueArr;  
}

但如果允许您更广泛地使用 C++ 标准库,您也可以考虑使用 std::sortstd::unique 的其他答案,尽管它们是 O(n log n) (而不是上面的 ~O(n) 解决方案)并破坏元素的顺序。


如果您想使用更灵活和标准驱动的方法,但具有 ~O(n) 复杂度并且不破坏元素的顺序,您可以将上述例程转换为以下类似标准的算法,即使是对于一个简单的面试问题来说有点牵强:

template<typename ForwardIterator>
ForwardIterator unordered_unique(ForwardIterator first, ForwardIterator last)
{
    typedef typename std::iterator_traits<ForwardIterator>::value_type value_type;
    std::unordered_set<value_type> unique;
    return std::remove_if(first, last, 
                          [&unique](const value_type &arg) mutable -> bool
                              { return !unique.insert(arg).second; });
}

然后您可以像 std::unique 一样以通常的擦除删除方式应用:

std::vector<int> values(...);
values.erase(unordered_unique(values.begin(), values.end()), values.end());

在不复制向量且无需事先排序的情况下删除唯一值。

【讨论】:

  • 很好的解释。我仍然不希望使用 太多 标准。我的注意力不只是在短时间内解决问题,而是专注于算法。
  • @rolandbishop 嗯,散列实际上是一种非常标准的数据结构,几乎在所有语言中都有(即使 C++ 标准委员会需要一些时间来实现这一点)。如果您有信心使用std::map,则不反对std::unordered_set
  • 我知道很傻,但是使用 unordered_setvector 有什么区别。如果问得太傻,请原谅我,但就只存储整数和提供 O(1) 而言,它们不是都做同样的事情吗? set有没有优势?
  • @rolandbishop 向量的问题是,它有 O(1) 插入,但只有 O(n) 查找(当你想搜索某个值是否在那里时)。另一方面,std::set 作为二叉树为您提供 O(log n) 插入和查找。但是 std::unordered_set 作为散列容器可以在最佳情况下为您提供 O(1) 插入和查找(当然取决于散列函数的质量)。
  • @rolandbishop 或者您也可以使用由输入数组中的值索引的std::vector。这确实也会给你 O(1) 的插入和查找。但问题是您需要知道输入数组中值的范围,并且这个范围不应该太大,而哈希集并不那么严重地依赖于键的范围。
【解决方案2】:

既然你问的是面试问题,我会说你没有得到这份工作。

const int size=10;
int arr[size]={1,2,2,4,2,5,6,5,7,1};

std::sort( &arr[0], &arr[size] );
int* new_end = std::unique( &arr[0], &arr[size] );

std::copy(
    &arr[0], new_end,
  , std::ostream_iterator< int >( std::cout, " " )
);

没有临时映射,没有临时向量,没有动态内存分配,大量代码编写,因此更容易编写和维护。

【讨论】:

  • 感谢您的回答。但是怎么来的?有那么低效吗?我相信我的解决方案是 O(n) 所以你能解释为什么我不会得到这份工作:)。再次感谢!
  • @rolandbishop: std::sort 通常(如果不总是)实现为快速排序。我相信std::stable_sort 通常是用 mergesort 的一些变体实现的,更糟糕​​的情况时间不那么严重。请注意,该标准为其算法建立了复杂性要求,因此只有当您能够满足这些最低要求时,滚动您自己的版本才有意义。
  • 我认为这个答案表明了对 C++ 的熟悉,而不是算法编写技巧。
  • @K-ballo 但这将是 O(nlogn)。使用一个好的哈希表,我们可以在 O(n) 中做到这一点,不是吗? BTW rolandbishop,地图不支持 O(1) 搜索。它被实现为红黑树。
  • @rolandbishop:如果给我你的代码,我会要求你为 std::unique 编写算法
【解决方案3】:
#include <algorithm>
#include <vector>

int main()
{
    std::vector<int> vec({1,2,3,2,4,4,5,7,6,6});
    std::sort(vec.begin(), vec.end());
    vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
    // vec = {1,2,3,4,5,6,7}
    return 0;
}
//works with C++11
// O(n log n)

【讨论】:

    【解决方案4】:

    就地移除对速度有好处 - 像这样(返回新大小):

    template <typename T, size_t N>
    size_t keep_unique(T (&array)[N])
    {
        std::unordered_set<T> found;
        for (size_t i = 0, j = 0; i < N; ++i)
            if (found.insert(array[i]).second))
                if (j != i) // (optional) avoid copy to self, as may be slower or unsupported by T
                    array[j++] = array[i];
                else
                    ++j;
        return j;
    }
    

    (对于较大的对象或无法安全复制的对象,可能需要和/或更快和更节省空间来将T*s 存储在 unordered_set 中 - 还必须提供取消引用比较运算符和散列函数。 )

    要形象化其工作原理,请考虑处理以下输入:

    1  3  6  3  5  6  0  2  1
             <--+<----+  |
                   <-----+
    

    上面的箭头表示产生答案所需的最小就地压缩:

    1  3  6  5  0  2
    

    这正是上面的算法所做的,查看[i] 中的所有元素,并跟踪它们需要复制到[j] 中的位置(以及有多少非重复项)。

    【讨论】:

      猜你喜欢
      • 2018-02-12
      • 1970-01-01
      • 2012-12-06
      • 2012-03-25
      • 2012-08-05
      • 2011-06-29
      • 2011-01-04
      相关资源
      最近更新 更多