在C ++中有效地规范化数组

时间:2014-11-04 07:20:35

标签: c++ arrays algorithm normalization

我正在寻找一种在C ++中有效规范化数组的方法,规范化意味着将所有数组值转换为小于或等于n的值。所以这个:

5235 223 1000 40 40

变为:

4 2 3 1 13 1 2 0 0

这是我的代码

vector<int> normalize_array(vector<int> arr){
    vector<int> tmp(arr), ret(arr.size());

    sort(tmp.begin(), tmp.end());

    for (int i = 0; i < arr.size(); ++i){
        vector<int>::iterator iter = find(tmp.begin(), tmp.end(), arr[i]);
        ret[i] = std::distance(tmp.begin(), iter);
    }

    return ret;
}

输出为4 2 3 0 0,上述代码无法很好地处理重复元素。 有没有更好的方法呢?

4 个答案:

答案 0 :(得分:3)

应用评论中所述的调整,并使用C ++ lambdas:

vector<int> normalize_array(const vector<int> &arr /* O(1) */) {
    vector<int> tmp(arr) /* O(N) */, ret(arr.size()) /* O(1) */;

    sort(tmp.begin(), tmp.end()); // O(N lg N)

    transform(arr.cbegin(), arr.cend(), ret.begin(), [&tmp](int x) {
        return distance(tmp.begin(), lower_bound(tmp.begin(), tmp.end(), x));
    }); // O(N lg N)

    return ret; // O(1) by move semantics
} // O(1) + O(N) + O(1) + O(N lg N) + O(N lg N) == O(N lg N)

Live Example

在下面的解决方案中,灵感来自@ sachse的答案,但使用C ++ 11,通过正确的规范化修复问题,生成4 2 3 1 1,因为我相信是预期的:

vector<int> normalize_array(const vector<int> &arr) {
    if (arr.empty())
        return {};

    vector<int> idx(arr.size()), ret(arr.size());

    iota(idx.begin(), idx.end(), 0);
    sort(idx.begin(), idx.end(),
         [&arr](int i, int j) { return arr[i] < arr[j]; });

    ret[idx[0]] = 1;
    for (size_t i = 1; i < arr.size(); ++i) {
        ret[idx[i]] = ret[idx[i - 1]] + (arr[idx[i]] != arr[idx[i - 1]]);
    }

    return ret;
}

Live Example

答案 1 :(得分:1)

如果你用这种方式定义规范化(数学家可能会说规范化是完全不同的),它就成了排序的问题(你正在有效地创建一个升序值的索引数组)。所以我想你应该看看排序算法并将它们用于你的情况。

您只需要考虑具有相同值的元素具有相同的索引 - 通常排序算法不会这样做。

答案 2 :(得分:1)

我建议使用以下具有O(n log n)复杂度的解决方案。唯一剩下的问题是,该算法不处理具有相同标准化值的重复值。

struct IndexComp {
    IndexComp(const std::vector<int>& vec) : m_vec(vec) {}
    bool operator() (int i,int j) { return (m_vec[i] < m_vec[j]);}
    const std::vector<int>& m_vec;
};

std::vector<int> normalize_array(const std::vector<int>& arr){
    std::vector<int> tmp, ret(arr.size());

    IndexComp indexComp(arr);

    for (int i = 0; i < arr.size(); ++i){
        tmp.push_back(i);
    }

    std::sort(tmp.begin(), tmp.end(), indexComp);

    for (int i = 0; i < arr.size(); ++i){
        ret[tmp[i]] = i;
    }

    return ret;
}

答案 3 :(得分:1)

这是一个O(n log n)解决方案,主要是基于标准库的解决方案,它处理OP中示例所暗示的重复值(尽管它来自0,而不是{{ 1}},因此示例输入产生1

3 1 2 0 0

它不是直接排序,而是将所有元素放入临时地图中。对它们进行排序并消除重复(template<typename It, typename OutIt> void normalize_array(It b, It e, OutIt out) { using T = typename It::value_type; std::map<T, int> tmp; std::transform(b, e, std::inserter(tmp, tmp.begin()), [](T v){ return std::make_pair(v, 0); }); int i = 0; for (auto& ent : tmp) ent.second = i++; std::transform(b, e, out, [&](T v){ return tmp[v]; }); } );我本可以使用一个集合,但是我想要下一步的映射,即按顺序对值进行编号(O(n log n))。然后,映射可用于查找每个值的索引。 (O(n))。

虽然解决方案在复杂性方面是最佳的,但可能有办法降低常数。

coliru

上查看