紫书ch5 STL初步

学了紫书一大半后回过头来看一下ch5,查漏补缺,主要是后面内容中时不时会出现“在本书第五章介绍过”的字段,然后一脸懵逼,现在就当来学习解惑了。

5.2.1 排序和检索

Uva 10474 大理石在哪儿

这道题主要可以用来熟悉一下algorithm头文件中的两个实用的函数sort和lower_bound

sort函数详解(史上最完整QAQ)

关于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之间可以直接赋值或者作为函数的返回值!

Uva 101 The Blocks Problem

一开始题目中的复位没有看懂,想了老半天,为什么要拿这个例子来将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++字母大小写转换方法

sscanf函数用法  C/C++中sscanf用法总结

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。

总结:

  1. 对输入的处理不一定读的时候就处理,可以先读下来再处理,这样手段更多
  2. 使用set必须定义"<"运算符,但由于string中已经定义,所以插入后自成字典序
  3. STL容器使用iterator遍历,iterator用法类似于指针

5.2.4 映射:map

map 就是从key到value的映射。因为重载了[ ]运算符,map像是数组的“高级版”。例如可以用一个map<string, int> month_name来表示“月份名字到月份编号”的映射,然后用month_name["July"]=7这样的方式赋值。(总觉得map=set+vector,一定是错觉)

Uva 156 反片语

刚做完前面set的题,看到这道题乍一下觉得这道题set也能做,后来仔细一想,set和map虽然都有判重机制,但是set无法根据键找值,并且set无法修改内部元素,做这道题还是稍稍不方便(效率上)。

我的思路是这样的:

  1. 将所有的的单词处理成“标准型”之后丢进map里面判重,如果已经存在,就将key的value修改为1,否则增加key,value为0。(这里的“标准型”是指为了判重方便认为规定的单词在map中存储的规则,即将单词变为小写后(题目要求case insensitive)再对单词字符串内部排序,变为标准型)

  2. 由于题目要求按照字典序输出原来的单词,所以需要将单词用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

 

队列

 

优先队列

 

 

python+opencv简谱识别音频生成系统源码含GUI界面+详细运行教程+数据 一、项目简介 提取简谱中的音乐信息,依据识别到的信息生成midi文件。 Extract music information from musical scores and generate a midi file according to it. 二、项目运行环境 python=3.11.1 第三方库依赖 opencv-python=4.7.0.68 numpy=1.24.1 可以使用命令 pip install -r requirements.txt 来安装所需的第三方库。 三、项目运行步骤 3.1 命令行运行 运行main.py。 输入简谱路径:支持图片或文件夹,相对路径或绝对路径都可以。 输入简谱主音:它通常在第一页的左上角“1=”之后。 输入简谱速度:即每分钟拍数,同在左上角。 选择是否输出程序中间提示信息:请输入Y或N(不区分大小写,下同)。 选择匹配精度:请输入L或M或H,对应低/中/高精度,一般而言输入L即可。 选择使用的线程数:一般与CPU核数相同即可。虽然python的线程不是真正的多线程,但仍能起到加速作用。 估算字符上下间距:这与简谱中符号的密集程度有关,一般来说纵向符号越稀疏,这个值需要设置得越大,范围通常在1.0-2.5。 二值化算法:使用全局阈值则跳过该选项即可,或者也可输入OTSU、采用大津二值化算法。 设置全局阈值:如果上面选择全局阈值则需要手动设置全局阈值,对于.\test.txt中所提样例,使用全局阈值并在后面设置为160即可。 手动调整中间结果:若输入Y/y,则在识别简谱后会暂停代码,并生成一份txt文件,在其中展示识别结果,此时用户可以通过修改这份txt文件来更正识别结果。 如果选择文件夹的话,还可以选择所选文件夹中不需要识别的文件以排除干扰
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值