C语言函数中,可以用qsort 来进行 结构体数组的排序,但是如果上升到C++中,对vector<NODE>的排序 qsort 就无能为力了。。。
不过C++ <algorithm> 函数库中的sort函数可以对 结构体vector进行排序。 前提是 要对 node 进行改造
例如 一个含有四项key 值的node 如下
struct node
{
string name;
int medal[3];
} ;
要对其以medal数组 降序排序,若 medal 数组完全相等,则以 name 的字典序排序
要调用 sort 函数对 node 进行排序,则 将node 改写为
struct node
{
string name;
int medal[3];
bool operator <(const node& other) const
{
if(medal[0] > other.medal[0]) return true;
if(medal[0] < other.medal[0]) return false;
if(medal[1] > other.medal[1]) return true;
if(medal[1] < other.medal[1]) return false;
if(medal[2] > other.medal[2]) return true;
if(medal[2] < other.medal[2]) return false;
if(name < other.name) return true;
return false;
}
} ;
例如在全局声明了一个vector<node> V;
则调用sort 如下 sort(V.begin(),V.end());
详细代码如下 (以TCO SRM 209 DIV2的500分题为例)
Problem Statement
The Olympic Games in Athens end tomorrow. Given the results of the olympic disciplines, generate and return the medal table. The results of the disciplines are given as a vector <string> results, where each element is in the format "GGG SSS BBB". GGG, SSS and BBB are the 3-letter country codes (three capital letters from 'A' to 'Z') of the countries winning the gold, silver and bronze medal, respectively. The medal table is a vector <string> with an element for each country appearing in results. Each element has to be in the format "CCO G S B" (quotes for clarity), where G, S and B are the number of gold, silver and bronze medals won by country CCO, e.g. "AUT 1 4 1". The numbers should not have any extra leading zeros. Sort the elements by the number of gold medals won in decreasing order. If several countries are tied, sort the tied countries by the number of silver medals won in decreasing order. If some countries are still tied, sort the tied countries by the number of bronze medals won in decreasing order. If a tie still remains, sort the tied countries by their 3-letter code in ascending alphabetical order.
Definition
Class:
MedalTable
Method:
generate
Parameters:
vector <string>
Returns:
vector <string>
Method signature:
vector <string> generate(vector <string> results)
(be sure your method is public)
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;
struct node
{
string name;
int medal[3];
bool operator <(const node& other) const
{
if(medal[0] > other.medal[0]) return true;
if(medal[0] < other.medal[0]) return false;
if(medal[1] > other.medal[1]) return true;
if(medal[1] < other.medal[1]) return false;
if(medal[2] > other.medal[2]) return true;
if(medal[2] < other.medal[2]) return false;
if(name < other.name) return true;
return false;
}
} ;
class MedalTable {
public:
set<string> Set;
vector<node> V;
vector <string> generate(vector <string>);
};
vector <string> MedalTable::generate(vector <string> results) {
vector<string> res;
for(int i = 0;i < results.size();i++)
{
string a,b,c;
istringstream s(results[i]);
s>> a>> b >> c;
Set.insert(a);
Set.insert(b);
Set.insert(c);
}
for(set<string>::iterator it = Set.begin();it != Set.end();it++)
{
node Node;
Node.name = *it;
Node.medal[0] = Node.medal[1] = Node.medal[2] = 0;
V.push_back(Node);
}
for(int i = 0;i < results.size();i++)
{
istringstream s(results[i]);
string a[3];
for(int j = 0;j < 3;j++)
{
s>>a[j];
for(int k = 0;k < V.size();k++)
{
if(a[j] == V[k].name)
V[k].medal[j] ++;
}
}
}
sort(V.begin(),V.end());
for(int i = 0;i < V.size();i++)
{
ostringstream outa;
outa<< V[i].name <<' '<<V[i].medal[0]<<' '<<V[i].medal[1]<<' '<<V[i].medal[2];
res.push_back(outa.str());
}
return res;
}
一些基础概念的定义
模板(Template)——类(以及结构等各种数据类型和函数)的宏(macro)。有时叫做甜饼切割机 (cookie cutter),正规的名称应叫做范型(generic)——一个类的模板叫做范型类(generic class),而一个函数的模板也自然而然地被叫做范型函数(generic function)。
STL——标准模板库,一些聪明人写的一些模板,现在已成为每个人所使用的标准C++语言中的一部分。
容器(Container)——可容纳一些数据的模板类。STL中有vector,set,map,multimap和deque等容器。
向量(Vector)——基本数组模板,这是一个容器。
游标(Iterator)——这是一个奇特的东西,它是一个指针,用来指向STL容器中的元素,也可以指向其它的元素。
Hello World程序
我愿意在我的黄金时间在这里写下我的程序:一个hello world程序。这个程序将一个字符串传送到一个字符向量中,然后每次显示向量中的一个字符。向量就像是盛放变长数组的花园,大约所有STL容器中有一半 是基于向量的,如果你掌握了这个程序,你便差不多掌握了整个STL的一半了。
//程序:vector演示一
//目的:理解STL中的向量
// #include "stdafx.h" -如果你使用预编译的头文件就包含这个头文件
#include <vector> // STL向量的头文件。这里没有".h"。
#include <iostream> // 包含cout对象的头文件。
using namespace std; //保证在程序中可以使用std命名空间中的成员。
char* szHW = "Hello World";
//这是一个字符数组,以”/0”结束。
int main(int argc, char* argv[])
{
vector <char> vec; //声明一个字符向量vector (STL中的数组)
//为字符数组定义一个游标iterator。
vector <char>::iterator vi;
//初始化字符向量,对整个字符串进行循环,
//用来把数据填放到字符向量中,直到遇到”/0”时结束。
char* cptr = szHW; // 将一个指针指向“Hello World”字符串
while (*cptr != '/0')
{ vec.push_back(*cptr); cptr++; }
// push_back函数将数据放在向量的尾部。
// 将向量中的字符一个个地显示在控制台
for (vi=vec.begin(); vi!=vec.end(); vi++)
// 这是STL循环的规范化的开始——通常是 "!=" , 而不是 "<"
// 因为"<" 在一些容器中没有定义。
// begin()返回向量起始元素的游标(iterator),end()返回向量末尾元素的游标(iterator)。
{ cout << *vi; } // 使用运算符 “*” 将数据从游标指针中提取出来。
cout << endl; // 换行
return 0;
}
push_back是将数据放入vector(向量)或deque(双端队列)的标准函数。Insert是一个与之类似的函数,然而它在所有容器中 都可以使用,但是用法更加复杂。end()实际上是取末尾加一(取容器中末尾的前一个元素),以便让循环正确运行——它返回的指针指向最靠近数组界限的数 据。就像普通循环中的数组,比如for (i=0; i<6; i++) {ar[i] = i;} ——ar[6]是不存在的,在循环中不会达到这个元素,所以在循环中不会出现问题。
STL的烦恼之一——初始化
STL令人烦恼的地方是在它初始化的时候。STL中容器的初始化比C/C++数组初始化要麻烦的多。你只能一个元素一个元素地来,或者先初始化一个普通数组再通过转化填放到容器中。我认为人们通常可以这样做:
//程序:初始化演示
//目的:为了说明STL中的向量是怎样初始化的。
#include <cstring> // <cstring>和<string.h>相同
#include <vector>
using namespace std;
int ar[10] = { 12, 45, 234, 64, 12, 35, 63, 23, 12, 55 };
char* str = "Hello World";
int main(int argc, char* argv[])
{
vector <int> vec1(ar, ar+10);
vector <char> vec2(str, str+strlen(str));
return 0;
}
在编程中,有很多种方法来完成同样的工作。另一种填充向量的方法是用更加熟悉的方括号,比如下面的程序:
//程序:vector演示二
//目的:理解带有数组下标和方括号的STL向量
#include <cstring>
#include <vector>
#include <iostream>
using namespace std;
char* szHW = "Hello World";
int main(int argc, char* argv[])
{
vector <char> vec(strlen(sHW)); //为向量分配内存空间
int i, k = 0;
char* cptr = szHW;
while (*cptr != '/0')
{ vec[k] = *cptr; cptr++; k++; }
for (i=0; i<vec.size(); i++)
{ cout << vec[i]; }
cout << endl;
return 0;
}
这个例子更加清晰,但是对游标(iterator)的操作少了,并且定义了额外的整形数作为下标,而且,你必须清楚地在程序中说明为向量分配多少内存空间。
命名空间(Namespace)
与STL相关的概念是命名空间(namespace)。STL定义在std命名空间中。有3种方法声明使用的命名空间:
1.用using关键字使用这个命名空间,在文件的顶部,但在声明的头文件下面加入:
using namespace std;
这对单个工程来说是最简单也是最好的方法,这个方法可以把你的代码限定在std命名空间中。
2.使用每一个模板前对每一个要使用的对象进行声明(就像原形化):
using std::cout;
using std::endl;
using std::flush;
using std::set;
using std::inserter;
尽管这样写有些冗长,但可以对记忆使用的函数比较有利,并且你可以容易地声明并使用其他命名空间中的成员。
3.在每一次使用std命名空间中的模版时,使用std域标识符。比如:
typedef std::vector VEC_STR;
这种方法虽然写起来比较冗长,但是是在混合使用多个命名空间时的最好方法。一些STL的狂热者一直使用这种方法,并且把不使用这种方法的人视为异类。一些人会通过这种方法建立一些宏来简化问题。
除此之外,你可以把using namespace std加入到任何域中,比如可以加入到函数的头部或一个控制循环体中。
一些建议
为了避免在调试模式(debug mode)出现恼人的警告,使用下面的编译器命令:
#pragma warning(disable: 4786)
另一条需要注意的是,你必须确保在两个尖括号之间或尖括号和名字之间用空格隔开,因为是为了避免同“>>”移位运算符混淆。比如
vector <list<int>> veclis;
这样写会报错,而这样写:
vector <list <int> > veclis;
就可以避免错误。
set是关联容器。其键值就是实值,实值就是键值,不可以有重复,所以我们不能 通过set的迭代器来改变set的元素的值,set拥有和list相同的特性:当对他进行插入和删除操作的时候,操作之前的迭代器依然有效。当然删除了的 那个就没效了。set的底层结构是RB-tree,所以是有序的。
stl中特别提供了一种针对set的操作的算法:交集set_intersection,并集set_union,差集set_difference。对称差集set_symeetric_difference,这些算法稍后会讲到。
一:set模板类的声明。
template <
class Key,
class Traits=less<Key>,
class Allocator=allocator<Key>
>
class set。
其中个参数的意义如下:
key:要放入set里的数据类型,可以是任何类型的数据。
Traits:这是一个仿函数(关于仿函数是什么,我后面的文章会讲到)。提供了具有比较功能的仿函数,来觉得元素在set里的排 列的顺序,这是一个可选的参数,默认的是std::less<key>,如果要自己提供这个参数,那么必须要遵循此规则:具有两个参数,返回 类型为bool。
Allocator:空间配置器,这个参数是可选的,默认的是std::allocator<key>.
二:set里的基本操作
我们可以通过下面的方法来实例化一个set对象
std::set<int> s;那个s这个对象里面存贮的元素是从小到大排序的,(因为用std::less作为比较工具。)
如果要想在s里面插入数据,可以用inset函数(set没用重载[]操作,因为set本生的值和索引是相同的)
s.insert(3);s.insert(5).....
因为set是集合,那么集合本身就要求是唯一性,所以如果要像set里面插入数据和以前的数据有重合,那么插入不成功。
可以通过下面的方法来遍历set里面的元素
std::set<int>::iterator it = s.begin();
while(it!=s.end())
{
cout<<*it++<<endl;//迭代器依次后移,直到末尾。
}
如果要查找一个元素用find函数,it = s.find(3);这样it是指向3的那个元素的。可以通过rbegin,rend来逆向遍历
std::set<int>::reverse_iterator it = s.rbegin();
while(it!=s.rend())
{cout<<*it++<<endl;}
还有其他的一些操作在这就不一一列出了。
三:与set相关的一组算法
set_intersection() :这个函数是求两个集合的交集。下面是stl里的源代码
template<class _InIt1,
class _InIt2,
class _OutIt> inline
_OutIt set_intersection(_InIt1 _First1, _InIt1 _Last1,
_InIt2 _First2, _InIt2 _Last2, _OutIt _Dest)
{ // AND sets [_First1, _Last1) and [_First2, _Last2), using operator<
for (; _First1 != _Last1 && _First2 != _Last2; )
if (*_First1 < *_First2)
++_First1;
else if (*_First2 < *_First1)
++_First2;
else
*_Dest++ = *_First1++, ++_First2;
return (_Dest);
}
这是个模板函数,从上面的算法可以看出,传进去的两个容器必须是有序的。_Dest指向输出的容器,这个容器必须是预先分配好空间的,否则会出错的,返回值指向保存结果的容器的尾端的下一个位置。eg.
set_union() :求两个集合的并集,参数要求同上。
std::set_difference():差集
set_symmetric_difference():得到的结果是第一个迭代器相对于第二个的差集并上第二个相当于第一个的差集。代码:
struct compare
{
bool operator ()(string s1,string s2)
{
return s1>s2;
}///自定义一个仿函数
};
int main()
{
typedef std::set<string,compare> _SET;
_SET s;
s.insert(string("sfdsfd"));
s.insert(string("apple"));
s.insert(string("english"));
s.insert(string("dstd"));
cout<<"s1:"<<endl;
std::set<string,compare>::iterator it = s.begin();
while(it!=s.end())
cout<<*it++<<" ";
cout<<endl<<"s2:"<<endl;
_SET s2;
s2.insert(string("abc"));
s2.insert(string("apple"));
s2.insert(string("english"));
it = s2.begin();
while(it!=s2.end())
cout<<*it++<<" ";
cout<<endl<<endl;
string str[10];
string *end = set_intersection(s.begin(),s.end(),s2.begin(),s2.end(),str,compare());//求交集,返回值指向str最后一个元素的尾端
cout<<"result of set_intersection s1,s2:"<<endl;
string *first = str;
while(first<end)
cout <<*first++<<" ";
cout<<endl<<endl<<"result of set_union of s1,s2"<<endl;
end = std::set_union(s.begin(),s.end(),s2.begin(),s2.end(),str,compare());//并集
first = str;
while(first<end)
cout <<*first++<<" ";
cout<<endl<<endl<<"result of set_difference of s2 relative to s1"<<endl;
first = str;
end = std::set_difference(s.begin(),s.end(),s2.begin(),s2.end(),str,compare());//s2相对于s1的差集
while(first<end)
cout <<*first++<<" ";
cout<<endl<<endl<<"result of set_difference of s1 relative to s2"<<endl;
first = str;
end = std::set_difference(s2.begin(),s2.end(),s.begin(),s.end(),str,compare());//s1相对于s2的差集
while(first<end)
cout <<*first++<<" ";
cout<<endl<<endl;
first = str;
end = std::set_symmetric_difference(s.begin(),s.end(),s2.begin(),s2.end(),str,compare());//上面两个差集的并集
while(first<end)
cout <<*first++<<" ";
cout<<endl;
}
STL实践指南
STL简介 STL (标准模版库,Standard Template Library)是当今每个从事C++编程的人需要掌握的一项不错的技术。我觉得每一个初学STL的人应该花费一段时间来熟悉它,比如,学习STL时会有 急剧升降的学习曲线,并且有一些命名是不太容易凭直觉就能够记住的(也许是好记的名字已经被用光了),然而如果一旦你掌握了STL,你就不会觉得头痛了。 和MFC相比,STL更加复杂和强大。
写这一部分是让一些初学计算机的读者在富有挑战性的计算机科学领域有一个良好的开端,而不必费力地了解那无穷无尽的行话术语和沉闷的规则,在这里仅仅把那些行话和规则当作STLer们用于自娱的创造品吧。 使用代码 一些基础概念的定义 模板(Template)——类(以及结构等各种数据类型和函数)的宏(macro)。有时叫做甜饼切割机 (cookie cutter),正规的名称应叫做范型(generic)——一个类的模板叫做范型类(generic class),而一个函数的模板也自然而然地被叫做范型函数(generic function)。 Hello World程序 我愿意在我的黄金时间在这里写下我的程序:一个hello world程序。这个程序将一个字符串传送到一个字符向量中,然后每次显示向量中的一个字符。向量就像是盛放变长数组的花园,大约所有STL容器中有一半 是基于向量的,如果你掌握了这个程序,你便差不多掌握了整个STL的一半了。 push_back是将数据放入vector(向量)或deque(双端队列)的标准函数。Insert是一个与之类似的函数,然而它在所有容器中 都可以使用,但是用法更加复杂。end()实际上是取末尾加一(取容器中末尾的前一个元素),以便让循环正确运行——它返回的指针指向最靠近数组界限的数 据。就像普通循环中的数组,比如for (i=0; i<6; i++) {ar[i] = i;} ——ar[6]是不存在的,在循环中不会达到这个元素,所以在循环中不会出现问题。 STL的烦恼之一——初始化 STL令人烦恼的地方是在它初始化的时候。STL中容器的初始化比C/C++数组初始化要麻烦的多。你只能一个元素一个元素地来,或者先初始化一个普通数组再通过转化填放到容器中。我认为人们通常可以这样做: 在编程中,有很多种方法来完成同样的工作。另一种填充向量的方法是用更加熟悉的方括号,比如下面的程序: 这个例子更加清晰,但是对游标(iterator)的操作少了,并且定义了额外的整形数作为下标,而且,你必须清楚地在程序中说明为向量分配多少内存空间。 命名空间(Namespace)与STL相关的概念是命名空间(namespace)。STL定义在std命名空间中。有3种方法声明使用的命名空间: 1.用using关键字使用这个命名空间,在文件的顶部,但在声明的头文件下面加入: 这对单个工程来说是最简单也是最好的方法,这个方法可以把你的代码限定在std命名空间中。 2.使用每一个模板前对每一个要使用的对象进行声明(就像原形化): 尽管这样写有些冗长,但可以对记忆使用的函数比较有利,并且你可以容易地声明并使用其他命名空间中的成员。 3.在每一次使用std命名空间中的模版时,使用std域标识符。比如: 这种方法虽然写起来比较冗长,但是是在混合使用多个命名空间时的最好方法。一些STL的狂热者一直使用这种方法,并且把不使用这种方法的人视为异类。一些人会通过这种方法建立一些宏来简化问题。 除此之外,你可以把using namespace std加入到任何域中,比如可以加入到函数的头部或一个控制循环体中。 一些建议为了避免在调试模式(debug mode)出现恼人的警告,使用下面的编译器命令: 另一条需要注意的是,你必须确保在两个尖括号之间或尖括号和名字之间用空格隔开,因为是为了避免同“>>”移位运算符混淆。比如 这样写会报错,而这样写: 就可以避免错误。 另一种容器——集合(set) 这是微软帮助文档中对集合(set)的解 释:“描述了一个控制变长元素序列的对象(注:set中的key和value是Key类型的,而map中的 key和value是一个pair结构中的两个分量)的模板类,每一个元素包含了一个排序键(sort key)和一个值(value)。对这个序列可以进行查找、插入、删除序列中的任意一个元素,而完成这些操作的时间同这个序列中元素个数的对数成比例关 系,并且当游标指向一个已删除的元素时,删除操作无效。” 如果你感兴趣的话,你可以将输出循环用下面的代码替换: .集合(set)虽然更强大,但我个人认为它有些不清晰的地方而且更容易出错,如果你明白了这一点,你会知道用集合(set)可以做什么。 所有的STL容器 容器(Container)的概念的出现早于模板(template),它原本是一个计算机科学领域中的一个重要概念,但在这里,它的概念和STL混合在一起了。下面是在STL中出现的7种容器: vector(向量)——STL中标准而安全的数组。只能在vector 的“前面”增加数据。 怎样在一个map中使用类? Map是一个通过key(键)来获得value(值)的模板类。
你应该重载尽可能多的运算符来满足特定模板的需要,比如,如果你想定义一个类作为 map中的键(key),你必须重载相关的运算符。但在这里不对重载运算符做过多讨论了。 TYPEDEF 如果你喜欢使用typedef关键字,下面是个例子: 编写代码的一个习惯就是使用大写字母和下划线来命名数据类型。 ANSI / ISO字符串 ANSI/ISO字符串在STL容器中使用得很普遍。这是标准的字符串类,并得到了广泛地提倡,然而在缺乏格式声明的情况下就会出问题。你必须使用“<<”和输入输出流(iostream)代码(如dec, width等)将字符串串联起来。 |
|