学了紫书一大半后回过头来看一下ch5,查漏补缺,主要是后面内容中时不时会出现“在本书第五章介绍过”的字段,然后一脸懵逼,现在就当来学习解惑了。
5.2.1 排序和检索
这道题主要可以用来熟悉一下algorithm头文件中的两个实用的函数sort和lower_bound
关于lower_bound( )和upper_bound( )的常见用法
顺便补充一下C++ greater<int>()和less<int>()如果sort和lower_bound作用的数组中的元素类型为内置类型,用上这个很方便
algorithm中还有很多使用的函数,如unique等等,可以用到后慢慢积累
5.2.2 不定长数组:vector
vector头文件中vector是一个不定长数组,可以用clear()清空,resize()改变大小,用push_back()和pop_back()在尾部添加和删除元素,用empty()判空。vector之间可以直接赋值或者作为函数的返回值!
一开始题目中的复位没有看懂,想了老半天,为什么要拿这个例子来将vector,这个在某个位置增删的操作不是链表嘛,后来看了题解只能感慨自己语文没学好。。
// UVa101 The Blocks Problem
// Rujia Liu
#include <cstdio>
#include <string>
#include <vector>
#include <iostream>
using namespace std;
const int maxn = 30;
int n;
vector<int> pile[maxn]; // 每个pile[i]是一个vector
// 找木块a所在的pile和height,以引用的形式返回调用者
void find_block(int a, int& p, int& h) {
for(p = 0; p < n; p++)
for(h = 0; h < pile[p].size(); h++)
if(pile[p][h] == a) return;
}
// 把第p堆高度为h的木块上方的所有木块移回原位
void clear_above(int p, int h) {
for(int i = h+1; i < pile[p].size(); i++) {
int b = pile[p][i];
pile[b].push_back(b); // 把木块b放回原位
}
pile[p].resize(h+1); // pile只应保留下标0~h的元素
}
// 把第p堆高度h及其上方的木块整体移动到p2堆的顶部
void pile_onto(int p, int h, int p2) {
for(int i = h; i < pile[p].size(); i++)
pile[p2].push_back(pile[p][i]);
pile[p].resize(h);
}
void print() {
for(int i = 0; i < n; i++) {
printf("%d:", i);
for(int j = 0; j < pile[i].size(); j++) printf(" %d", pile[i][j]);
printf("\n");
}
}
int main() {
int a, b;
cin >> n;
string s1, s2;
for(int i = 0; i < n; i++) pile[i].push_back(i);
while(cin >> s1 >> a >> s2 >> b) {
int pa, pb, ha, hb;
find_block(a, pa, ha);
find_block(b, pb, hb);
if(pa == pb) continue; // 非法指令
if(s2 == "onto") clear_above(pb, hb);
if(s1 == "move") clear_above(pa, ha);
pile_onto(pa, ha, pb);
}
print();
return 0;
}
上述代码有一个值得学习的技巧:输入一共有4种指令,如果完全独立地处理各指令,代码会变得冗长而且易错。更好的方法是提取出指令之间的共同点,编写函数以减少代码重复。
C++STL中vector容器 begin()与end()函数、front()与back()的用法
set头文件中的set和map头文件中的map分别是集合与映射。二者都支持inset、find、count和remove操作,并且可以按照从小到大的顺序循环遍历其中的元素。map还提供了"[]"运算符,使得map可以像数组一样使用。事实上,map也称为“关联数组”。
5.2.3 集合:set
集合和映射也是两个常用的容器,集合中每个元素最多出现一次。和sort一样,自定义类型也可以构造set,但是同样必须定义"<"运算符。
Uva 10815 Andy's First Dictionary
#include <iostream>
#include <string>
#include <regex>
#include<set>
using namespace std;
int main()
{
string s;
regex e("([a-zA-Z]*)");
regex_iterator<string::iterator> rend;
set<string> dict;
while(getline(cin, s))
{
for (int i = 0; i < s.length(); i++)
if (isalpha(s[i])) s[i] = tolower(s[i]);
regex_iterator<string::iterator> rit(s.begin(), s.end(), e);
while (rit != rend) {
dict.insert(rit->str());
++rit;
}
}
set<string>::iterator it = dict.begin(); ++it; //不知道为什么set中会放入'\n'。。
for ( ;it != dict.end(); ++it)
cout << *it << "\n";
}
看到这道题,就一直想着正则匹配,于是搜了一堆资料。。(本来想用万能的getchar一了百了,但是想想代码会很low)
C++中set用法详解 getline()详解 C++字母大小写转换方法
C++常用正则表达式匹配 C++11 正则表达式简单运用 regex_iterator
#include<iostream>
#include<string>
#include<set>
#include<sstream>
using namespace std;
set<string> dict;
int main() {
string s, buf;
while(cin >> s) {
for(int i = 0; i < s.length(); i++)
if(isalpha(s[i])) s[i] = tolower(s[i]); else s[i] = ' ';
stringstream ss(s);
while(ss >> buf) dict.insert(buf);
}
for(set<string>::iterator it = dict.begin(); it != dict.end(); ++it)
cout << *it << "\n";
return 0;
}
这代码我是真的服气,直接将非字母字符置空,充分利用遇输入流遇空白符结束的特点(下来一定好好学习一下string和stringstream)orz。
总结:
- 对输入的处理不一定读的时候就处理,可以先读下来再处理,这样手段更多
- 使用set必须定义"<"运算符,但由于string中已经定义,所以插入后自成字典序
- STL容器使用iterator遍历,iterator用法类似于指针
5.2.4 映射:map
map 就是从key到value的映射。因为重载了[ ]运算符,map像是数组的“高级版”。例如可以用一个map<string, int> month_name来表示“月份名字到月份编号”的映射,然后用month_name["July"]=7这样的方式赋值。(总觉得map=set+vector,一定是错觉)
刚做完前面set的题,看到这道题乍一下觉得这道题set也能做,后来仔细一想,set和map虽然都有判重机制,但是set无法根据键找值,并且set无法修改内部元素,做这道题还是稍稍不方便(效率上)。
我的思路是这样的:
-
将所有的的单词处理成“标准型”之后丢进map里面判重,如果已经存在,就将key的value修改为1,否则增加key,value为0。(这里的“标准型”是指为了判重方便认为规定的单词在map中存储的规则,即将单词变为小写后(题目要求case insensitive)再对单词字符串内部排序,变为标准型)
-
由于题目要求按照字典序输出原来的单词,所以需要将单词用word数组保存下来,又因为直接对字符串交换效率有些不高,并且由于map中的元素是自动排序的,所以需要额外开一个数组lword保存键值(处理过的单词)。这样一来对word排序后,lword也要跟着变,否则索引就会出问题,典型的多属性(结构体型)排序,故采用间接排序的方法(即开一个数组保存索引,根据数组内容对索引排序),可以大大简化逻辑,提高效率。
#include<iostream>
#include<sstream>
#include<vector>
#include<map>
#include<algorithm>
#include<string>
using namespace std;
vector<string> word,lword;
int cmp(int i, int j)
{
return word[i] < word[j];
}
int main()
{
string buf;
vector<int> ref;
map<string, int> dict;
int cnt = 0;
while (cin >> buf && buf[0] != '#')
{
word.push_back(buf);
ref.push_back(cnt++);
for (int i = 0; i < buf.length(); i++)
buf[i] = tolower(buf[i]);
sort(buf.begin(), buf.end());
lword.push_back(buf);
if (dict.count(buf)) dict[buf] = 1;
else dict[buf] = 0;
}
sort(ref.begin(),ref.end(),cmp);
for (int i = 0; i < ref.size(); i++)
if (!dict[lword[ref[i]]]) cout << word[ref[i]] << endl;//cin读入string中是没有'\0'的,不能用%s输出
}
这回lrj代码的效率总算没我高了嘿嘿,他的思路和我最初的思路一模一样。只不过代码风格还是要学习啊,把标准化过程写成一个函数,这样可读性、逻辑性更高求便于扩展、调试。
// UVa156 Ananagrams
// Rujia Liu
// 题意:输入一些单词,找出所有满足如下条件的单词:该单词不能通过字母重排得到输入文本中的另外一个单词
// 算法:把每个单词“标准化”,即全部转化为小写字母然后排序,然后放到map中进行统计
#include<iostream>
#include<string>
#include<cctype>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
map<string,int> cnt;
vector<string> words;
// 将单词s进行“标准化”
string repr(string s) {
string ans = s;
for(int i = 0; i < ans.length(); i++)
ans[i] = tolower(ans[i]);
sort(ans.begin(), ans.end());
return ans;
}
int main() {
int n = 0;
string s;
while(cin >> s) {
if(s[0] == '#') break;
words.push_back(s);
string r = repr(s);
if(!cnt.count(r)) cnt[r] = 0;
cnt[r]++;
}
vector<string> ans;
for(int i = 0; i < words.size(); i++)
if(cnt[repr(words[i])] == 1) ans.push_back(words[i]);
sort(ans.begin(), ans.end());
for(int i = 0; i < ans.size(); i++)
cout << ans[i] << "\n";
return 0;
}
顺便补一些基础知识。。对于cin提取输入流遇到空格的问题 C++中 string对象的大小比较
STL还提供了3种特殊的数据结构:栈、队列与优先队列。
栈
Uva 12096 The SetStack Computer
队列
优先队列