算法
本文介绍一些在使用算法中常见的错误已经给出相应的解决的方法,同时提供一些提高使用算法能力的建议。
1. 确保目标区间足够大
end()返回的实际上是不可使用的地址,不能在上面创建对象。使用插入型的迭代器可以不断地增加目标区间。
#include <iostream>
#include <vector>
using namespace std;
int change(int i) {
return i;
}
int main() {
vector<int> values{2, 3, 5};
vector<int> result{1, 2, 4};
transform(values.begin(), values.end(), result.end(), change);
return 0;
}
这段代码出现的错误非常的常见。
我们希望把values的每个值通过一个函数形成另一个值并且放在result的末尾。但实际上,这不是不能通过编译的。原因在于result.end()并不是返回一个可用的地址。在上面创建对象是不被允许的。
正确的做法是使用back_inserter,返回一个迭代器。
#include <iostream>
#include <vector>
using namespace std;
int change(int i) {
return i;
}
int main() {
vector<int> values{2, 3, 5};
vector<int> result{1, 2, 4};
transform(values.begin(), values.end(), back_inserter(result), change);
copy(result.begin(), result.end(), ostream_iterator<int> (cout, " "));
return 0;
}
back_inserter使用的push_back函数,所以只有提供了这个函数的容器才能使用back_inserter。例如,vector, string, deque, list.
同样地如果想在头部添加相应区间的元素可以调用front_inserter。但必须是提供了push_front函数的容器才能使用,例如deque, list。
#include <iostream>
//#include <vector>
#include <list>
using namespace std;
int change(int i) {
return i;
}
int main() {
list<int> values{2, 3, 5};
list<int> result{1, 2, 4};
transform(values.begin(), values.end(), front_inserter(result), change);
copy(result.begin(), result.end(), ostream_iterator<int> (cout, " "));
return 0;
}
需要注意的是,这样写,输入的区间顺序会相反。做一个简单的调整就好了。
transform(values.rbegin(), values.rend(), front_inserter(result), change);
更自由的选择其实是使用inserter。可以自由决定在什么位置输入元素。
#include <iostream>
//#include <vector>
#include <list>
using namespace std;
int change(int i) {
return i;
}
int main() {
list<int> values{2, 3, 5};
list<int> result{1, 2, 4};
transform(values.begin(), values.end(), inserter(result, result.begin()), change);
copy(result.begin(), result.end(), ostream_iterator<int> (cout, " "));
return 0;
}
最后一点需要强调的是。就算使用了reserve预留空间,也不代表.end()是可用地址。reserve本质上来说并没有为相应区间创建对象,他只是告诉容器,给我准备这么多的容器,我要用的时候我可以很快地创建对象。
2. 了解与排序有关的选择
sort
这是最典型的不稳定排序方法,完全排序,对数时间完成,也非常常见,不需要多提。
partial_sort
只保证前几个元素是按照给定的排序方法排序的,其他元素不改变他们的相对位置。
#include <iostream>
#include <vector>
using namespace std;
int change(int i) {
return i;
}
bool compare(const int& i, const int& j) {
return i < j;
}
int main() {
vector<int> values{2, 3, 5};
vector<int> result{1, 2, 4, 2, 3, 6, 5};
partial_sort(result.begin(), result.begin() + 3, result.end(), compare);
copy(result.begin(), result.end(), ostream_iterator<int> (cout, " "));
return 0;
}
// 1 2 2 4 3 6 5
这样做是为了处理当只要前几个对象排好序的情况,可以提高效率。
nth_element
API里说这个函数只要进行部分排序,但我在测试中发现他实际上会整个进行排序,具体的原因不详。
#include <iostream>
#include <vector>
using namespace std;
int change(int i) {
return i;
}
bool compare(const int& i, const int& j) {
return i < j;
}
int main() {
vector<int> values{2, 3, 5};
vector<int> result{2, 1, 4, 1, 3, 6, 5};
nth_element(result.begin(), result.begin() + 1, result.end(), compare);
copy(result.begin(), result.end(), ostream_iterator<int> (cout, " "));
return 0;
}
// 1 1 2 3 4 5 6
partition
这个函数可以得到满足条件的一段区间,但是他们中的排序情况是不可知的。他会返回一个迭代表示第一个不符合条件的元素的迭代器。
#include <iostream>
#include <vector>
using namespace std;
int change(int i) {
return i;
}
bool compare(const int& i) {
return i == 1 || i == 2 || i == 3;
}
int main() {
vector<int> values{2, 3, 5};
vector<int> result{2, 1, 4, 1, 3, 6, 5};
partition(result.begin(), result.end(), compare);
copy(result.begin(), result.end(), ostream_iterator<int> (cout, " "));
return 0;
}
// 2 1 3 1 4 6 5
可以推断出他排列的方式就是把不符合条件的值和符合条件的值互换。
stable_partition
这个函数可以弥补在partion中出现的相对关系不明确的情况。这个函数会保持原有的相对关系。
#include <iostream>
#include <vector>
using namespace std;
int change(int i) {
return i;
}
bool compare(const int& i) {
return i == 1 || i == 2 || i == 3;
}
int main() {
vector<int> values{2, 3, 5};
vector<int> result{2, 1, 4, 1, 3, 6, 5};
stable_partition(result.begin(), result.end(), compare);
copy(result.begin(), result.end(), ostream_iterator<int> (cout, " "));
return 0;
}
// 2 1 1 3 4 6 5
需要注意的是,sort, stable_sort, partial_sort, nth_element都必须是提供随机访问的迭代器。所以list不可用。当然了,你可以把list的数据存入vector中,在调用相应的函数,再调回来。
list本身已经提供了sort的成员函数,进行排序。
3. remove之后一定要使用erase
remove并没有真正的删除元素。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> values{2, 3, 5};
vector<int> result{2, 1, 4, 1, 3, 6, 5};
remove(result.begin(), result.end(), 1);
copy(result.begin(), result.end(), ostream_iterator<int> (cout, " "));
return 0;
}
// 2 4 3 6 5 6 5
和预期的一样,remove并没有删除元素!
原因就在于,remove只接受迭代器,他根本不知道这个迭代器是属于什么容器的,他当然就不能删除元素了,他只能把不需要的元素移动到后面,并且确保在容器中不再有这个值。
那个这个移除是怎么做到的呢?
其实很简单,就是把要移除的元素和后面不需要移除的元素进行交换赋值!(不仅仅是交换)形象地说,就是要删除的元素留下了一个洞,需要后面的元素来填!
由此可以见,remove以后其实整个容器的数据结构已经完全不同了,如果此时不及时地删除,很有可能会造成不可挽回的结果,因为你再也不知道需要被删除的元素在哪里,有多少个。
所以,正确的做法应该是:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> values{2, 3, 5};
vector<int> result{2, 1, 4, 1, 3, 6, 5};
result.erase(remove(result.begin(), result.end(), 1), result.end());
copy(result.begin(), result.end(), ostream_iterator<int> (cout, " "));
return 0;
}
// 2 4 3 6 5
一切正常!
那么继续推演下去,如果容器里面存放的是指针呢?
很不幸的是,那么必然会导致泄露。除非你提前把不需要的元素找出来,释放掉相应的内存。所以,更加高效的做法,就是不放入原生态的指针,而是放入智能指针!没错,又是智能指针!原生态指针在编程使用中总是会出现很多很多的问题,而智能指针可以很好地解决这些问题。
4. 忽略大小写的字符串比较
在这里我们使用一个非常常的函数lexicographical_compare。
#include <iostream>
#include <vector>
using namespace std;
bool Compare(const char& a, const char& b) {
return tolower(static_cast<unsigned char> (a)) <
tolower(static_cast<unsigned char> (b));
}
bool cStringCompare(string& s1, string &s2) {
return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), Compare);
}
int main() {
string s1 = "Yan";
string s2 = "yan";
cout << cStringCompare(s1, s2);
return 0;
}
在C++中,因为char可以是有符号的也可以是无符号的,所以我们必须要他们都转变为无符号的然后才能进行比较。