C++ 对元素进行排序,但保持某些元素不变
功能C++ 对元素进行排序,但保持某些元素不变,c++,sorting,templates,lambda,C++,Sorting,Templates,Lambda,功能 template <typename Container, typename Comparator, typename Predicate> void sortButKeepSomeFixed (Container& c, const Comparator& comp, const Predicate& pred) 但是时间复杂度是O(N^2)(我认为)。有人能将这里的时间复杂度平均提高到O(NlogN)吗?换句话说,使用递归或类似的方法,找到一个总体
template <typename Container, typename Comparator, typename Predicate>
void sortButKeepSomeFixed (Container& c, const Comparator& comp, const Predicate& pred)
但是时间复杂度是O(N^2)(我认为)。有人能将这里的时间复杂度平均提高到O(NlogN)吗?换句话说,使用递归或类似的方法,找到一个总体上更好的算法
或者更好的方法是取出满足pred
的元素,对剩下的std::sort
进行排序,然后将提取的元素放回原始位置?这会更有效率吗,还是会让事情变得更糟
更新:
这是基于Beta的建议(对未通过pred的迭代器进行排序)。但是,尽管通过pred的元素确实保持不变,但最后的排序是不正确的
template <typename Container, typename Comparator, typename Predicate>
void sortButKeepSomeFixed (Container& c, const Comparator& comp, const Predicate& pred) {
std::vector<typename Container::iterator> iterators;
for (typename Container::iterator it = c.begin(); it != c.end(); ++it) {
if (!pred(*it))
iterators.emplace_back(it);
}
std::vector<typename Container::iterator> originalIterators = iterators;
std::sort(iterators.begin(), iterators.end(),
[comp](const typename Container::iterator& x, const typename Container::iterator& y)
{return comp(*x, *y);});
for (int i = 0; i < originalIterators.size(); i++)
*originalIterators[i] = *iterators[i];
}
模板
void sortButKeepSomeFixed(容器和c、常量比较器和comp、常量谓词和pred){
向量迭代器;
对于(typename容器::迭代器it=c.begin();it!=c.end();++it){
如果(!pred(*it))
迭代器。向后放置(it);
}
std::vector originalIterators=迭代器;
std::sort(迭代器.begin(),迭代器.end(),
[comp](常量typename容器::迭代器&x,常量typename容器::迭代器&y)
{返回comp(*x,*y);});
对于(int i=0;i
不正确的输出是
11 9 9 8 11 3 20 2 9
,而它应该是11 9 7 8 5 3 20 2 1
,这是基于Beta使用迭代器排序的想法,尽管我不确定时间复杂度是多少。它也不适用于所有容器,例如std::set、std::map
template <typename Container, typename Comparator, typename Predicate>
void sortButKeepSomeFixed (Container& c, const Comparator& comp, const Predicate& pred) {
std::vector<typename Container::value_type> toSort;
std::vector<typename Container::iterator> iterators;
for (typename Container::iterator it = c.begin(); it != c.end(); ++it) {
if (!pred(*it)) {
toSort.emplace_back(*it);
iterators.emplace_back(it);
}
}
std::sort(toSort.begin(), toSort.end(), comp);
for (std::size_t i = 0; i < toSort.size(); i++)
*iterators[i] = toSort[i];
}
std::vector<int> vector = {5,7,1,8,9,3,20,2,11};
std::array<int, 9> array = {5,7,1,8,9,3,20,2,11};
std::list<int> list = {5,7,1,8,9,3,20,2,11};
std::set<int> set = {5,7,1,8,9,3,20,2,11};
std::map<double, int> map = { {1.5,5}, {1.2,7}, {3.5,1}, {0.5,8}, {5.2,9}, {7.5,3}, {0.1,20}, {1.8,2}, {2.4,11} };
template <typename Container>
void test (Container& container) {
sortButKeepSomeFixed (container,
std::greater<int>(), // Ordering condition.
[](int x) {return x % 2 == 0;}); // Those that shall remain fixed.
for (int x : container) std::cout << x << ' ';
std::cout << '\n';
}
int main() {
test(vector); // 11 9 7 8 5 3 20 2 1
test(array); // 11 9 7 8 5 3 20 2 1
test(list); // 11 9 7 8 5 3 20 2 1
test(set); // Does not compile.
sortButKeepSomeFixed (map,
[](const std::pair<double, int>& x, const std::pair<double, int>& y) {return x.second > y.second;},
[](const std::pair<double, int>& x) {return x.second % 2 == 0;});
for (const std::pair<double, int>& x : map)
std::cout << "(" << x.first << "," << x.second << ") "; // Does not compile.
}
测试:
struct GreaterThan { bool operator()(int x, int y) const {return x > y;} };
std::set<int, GreaterThan> newSet = sortButRemoveSomeElements (set,
GreaterThan{}, // Ordering condition.
[](int x) {return x % 2 == 0;}); // Those that shall be removed.
for (int x : newSet) std::cout << x << ' '; // 11 9 7 5 3 1
struct GreaterThan{bool operator()(intx,inty)const{return x>y;};
std::set newSet=sortButRemoveSomeElements(set,
大于{},//排序条件。
[](int x){返回x%2==0;});//那些应该被移除的。
对于(intx:newSet)std::cout,这是一个有趣的例子。我首先尝试编写IMO正确的方法,使用只跳过满足谓词的元素的方法。事实证明,这是一个相当具有挑战性的问题,至少在我做这件事的时候,我会在手机上写下它
基本上,这将产生类似于Eric Niebler的代码
但也有一种更简单、直接的方法,您正试图使用上面提到的方法。非工作解决方案的问题是,它正在更改(其余排序的)迭代器在最后一个for
循环中赋值时指向的值。可以通过复制来避免此问题,如在我的代码中:
int main(int, char **) {
vector<int> input {1,2,3,4,5,6,7,8,9};
vector<reference_wrapper<int>> filtered{begin(input), end(input)};
filtered.erase(remove_if(begin(filtered), end(filtered),
[](auto e) {return e%2==0;}), end(filtered));
vector<int> sorted{begin(filtered), end(filtered)};
// change that to contain reference wrappers to see the issue
sort(begin(sorted), end(sorted),
greater<int>{});
transform(begin(filtered), end(filtered),
begin(sorted),
begin(filtered),
[](auto to, auto from) {
to.get() = from; return to;});
copy(begin(input), end(input),
ostream_iterator<int>{cout, ", "});
return 0;
}
在构造时,该类存储指向其参数的指针,在使用复制赋值运算符时也会修改该参数。但是,当一个实例被复制构造时,就会生成一个被引用(由另一个引用)值的堆分配副本。这样,就可以使用类似于的代码交换两个引用值
Value a, b;
copyable_ref<Value> ref_a{a}, ref_b{b};
copyable_ref<Value> temp{ref_a};
ref_a = ref_b;
ref_b = temp;
// a and b are swapped
最后,虽然这很有效,但我可能不会在生产代码中使用它。我特别惊讶的是,std::sort
没有使用我自己的swap
实现,这导致了这个冒险的复制构造函数
您不能将您的代码概括为适用于集合和映射:这些集合和映射是按设计排序的,它们需要固定的顺序才能正常工作。无序的变体是无序的,因此无法维持秩序。但是您可以(只要不修改容器)在向量中使用std::reference\u wrapper
s来提供数据的排序“视图”。您的“更好的想法”将非常有效,O(N logN)。O(N logN)表示std::sort
本身,但是,删除元素然后将它们放回原始位置所花费的时间增加了很多,不是吗?如果您使用类似于向量的东西,而不是自己处理插入/删除元素,则插入/删除会摊销O(N)
的复杂性,所以这很好。事实上,不,对不起。如果要在适当的位置进行复制,例如,如果不创建原始阵列的副本,则需要复制周围的内容。如果一个接一个地进行删除,那么每次插入/删除都将是O(N)
,因此总的复杂性将是O(N^2)。我建议您为那些没有通过谓词的人创建一个新数组,如果运行性能是您的优先级,那么内存复杂性是O(N)
,而不是O(1)
。有时应该问“为什么?”:此实现支持大多数元素不受影响的情况。其他替代方法是复制回去,重新评估Pred
以找到需要跳过的元素(我的直觉认为这是最好的通用版本),或者将迭代器保留在需要跳过的位置(这有利于Pred
昂贵的情况)。对映射和集进行排序确实没有意义。因为对于集合,它们已经按元素本身排序;对于映射,它们已经按键排序。这就是为什么我们使用映射和集合,总是保持它们的排序,所以每当我们需要查找/插入/删除元素时,总是O(logN)
。好的,那么我建议对于集合、映射等,只需删除满足pred的元素并创建一个新的集合/map/。。。将Compare作为其键\u Compare类型。在这方面,我已经编写了一个适用于集合的函数,但是推广?@Daniel Jour感谢您修复了我最初的迭代器尝试。至于您的自定义迭代器尝试,您认为它可以修复吗?它看起来非常有趣,如果你认为它是可以修复的,我会尝试自己修复它(这将是“最酷”的解决方案)。据我所知,你已经在自己的答案中这样做了。这基本上就是我所做的。让我烦恼的是,我希望有必要的副本。这不应该是那样的,我正在努力解决这个问题。我知道,我还没有准备好。不幸的是,我没有叉子,所以前一个暂时丢了。@Daniel Jour你可能对这个更感兴趣
int main(int, char **) {
vector<int> input {1,2,3,4,5,6,7,8,9};
vector<reference_wrapper<int>> filtered{begin(input), end(input)};
filtered.erase(remove_if(begin(filtered), end(filtered),
[](auto e) {return e%2==0;}), end(filtered));
vector<int> sorted{begin(filtered), end(filtered)};
// change that to contain reference wrappers to see the issue
sort(begin(sorted), end(sorted),
greater<int>{});
transform(begin(filtered), end(filtered),
begin(sorted),
begin(filtered),
[](auto to, auto from) {
to.get() = from; return to;});
copy(begin(input), end(input),
ostream_iterator<int>{cout, ", "});
return 0;
}
template <class T>
class copyable_ref {
public:
copyable_ref(T& ref) noexcept
: _ptr(std::addressof(ref)), _copied(false) {}
copyable_ref(T&&) = delete;
copyable_ref(const copyable_ref& x) noexcept
: _ptr (new int(*x._ptr)), _copied (true) {
}
~copyable_ref() {
if (_copied) {
delete _ptr;
}
}
copyable_ref& operator=(const copyable_ref& x) noexcept {
*_ptr = *x._ptr;
}
operator T& () const noexcept { return *_ptr; }
T& get() const noexcept { return *_ptr; }
private:
T* _ptr;
bool _copied;
};
Value a, b;
copyable_ref<Value> ref_a{a}, ref_b{b};
copyable_ref<Value> temp{ref_a};
ref_a = ref_b;
ref_b = temp;
// a and b are swapped
vector<int> input {1,2,3,4,5,6,7,8,9};
vector<copyable_ref<int>> sorted;
sorted.reserve(input.size());
for (auto & e : input) {
if (e % 2 != 0) {
sorted.emplace_back(e);
}
}
sort(begin(sorted), end(sorted),
greater<int>{});
copy(begin(input), end(input),
ostream_iterator<int>{cout, ", "});
cout << endl;
// 9 2 7 4 5 6 3 8 1