【C++ Primer】第十一章 关联容器 (练习)

C++ Primer 5th 随堂练习

【C++ Primer】第一章 开始 (练习)

【C++ Primer】第二章 变量和基本类型 (练习)

【C++ Primer】第三章 字符串、向量和数组 (练习)

【C++ Primer】第四章 表达式 (练习)

【C++ Primer】第五章 语句 (练习)

【C++ Primer】第六章 函数 (练习)

【C++ Primer】第七章 类 (练习)

【C++ Primer】第八章 IO 库 (练习)

【C++ Primer】第九章 顺序容器 (练习)

【C++ Primer】第十章 泛型算法 (练习)

【C++ Primer】第十一章 关联容器 (练习)


第十一章 泛型算法


练习 11.1

描述 map 和 vector 的不同。

解答

两类容器的根本差别在于,顺序容器中的元素是 “顺序” 存储的(链表容器中的元素虽然不是在内存中 “连续” 存储的,但仍然是按 “顺序” 存储的)。理解 “顺序” 的关键,是理解容器支持的操作形式以及效率。

 vector 这种 顺序容器,元素在其中顺序存储,每个元素都有唯一对应的位置编号,所有操作都按索引/下标编号(位置)进行。例如,访问/获取元素(头、尾、用下标获取任意位置)、插入删除元素(头、尾、任意位置)、遍历元素(按元素位置顺序逐一访问)。底层的数据结构是 数组链表,简单但已能保证上述操作的高效。而对于依赖值的元素访问,例如查找(搜索)给定值(find),需要通过遍历完成,效率不佳 (时间复杂度O(n))。

map 这种 关联容器,就是为了高效实现 “按值访问元素” 这类操作而设计的,元素在其中按关键字存储,关键字与元素值建立起对应关系 ——“关联”。底层数据结构是 红黑树哈希表 等,可高效实现按关键字查找、添加、删除元素值等操作 (时间复杂度O(1))。


练习 11.2

分别给出最适合使用 list、vector、deque、map 以及 set 的例子。

解答

若元素很小(如 int),大致数量预先可知,在程序运行过程中不会剧烈变化,大部分情况下只在末尾添加或删除,需要频繁访问任意位置的元素,则 vector 效率最高。若需要频繁在头部和尾部增删元素,则 deque 为最佳选择。

若元素较大(如大的类对象),数量预先未知,或是程序运行过程中频繁变化,对元素的访问更多是顺序访问全部或很多元素,则 list 很合适。

map 属于 哈希映射 类型,适合对一些对象按它们的某个特征进行访问的情形。例如:按学生的姓名来查询学生信息,即可将学生姓名作为关键字,将学生信息作为元素值,保存在 map 中。再比如统计单词出现的次数等。

set 属于 哈希集合 类型。当需要保存特定的值集合 —— 通常是满足/不满足某种要求的值集合,用 set 最方便,如黑名单,字母表等。


练习 11.3

编写你自己的单词计数程序。

解答

源程序:

#include <iostream>
#include <algorithm>
#include <map>
#include <string>
#include <cctype>

using namespace std;

bool is_word(const string word)
{
    for (auto c : word) {
        if (!isalpha(c))  // 若任一字符不为字母, 则不是单词
            return false;
    }
    return true;
}

int main(int argc, char *argv[])
{   
    map<string, int> word_count;  // 哈希字典
    // 记录
    string word;
    while (cin >> word) {
        if (is_word(word))  // 只记录字母
            ++word_count[word];  // 相当于 Python 中默认类型为 string 的默认字典 defaultdict
    }
    // 打印
    for (const auto w : word_count) {
        cout << w.first << " occurs " << w.second << " time(s)" << endl;
    }

    system("pause");
    return 0;
}

控制台交互:

hello world my dear lady 666 you are my entire world
^Z
are occurs 1 time(s)
dear occurs 1 time(s)
entire occurs 1 time(s)
hello occurs 1 time(s)
lady occurs 1 time(s)
my occurs 2 time(s)
world occurs 2 time(s)
you occurs 1 time(s

练习 11.4

扩展你的程序,忽略大小写和标点。例如,"example."、"example," 和 "Example" 应该递增相同的计数器。

解答

源程序:(存在问题)

#include <iostream>
#include <algorithm>
#include <map> 
#include <string>
#include <cctype>

using namespace std;

void word_count_plus(map<string, int> &m)
{
    // 输入内容
    string word;
    while (cin >> word) {
        // 字母小写化
        for (auto &ch : word) 
            ch = tolower(ch);
        // 删除标点
        // 注意:必须写成 ::ispunct 而不能仅为 ispunct !!!
        word.erase(remove_if(word.begin(), word.end(), ::ispunct), word.end());
        // 计数加一
        ++m[word];
	}
    // 打印结果
    for (const auto w : m) 
        cout << w.first << " : " << w.second << endl;
}

int main(int argc, char *argv[])
{   
    map<string, int> word_count;
    word_count_plus(word_count);

    system("pause");
    return 0;
}

小贴士

remove_if():移除序列 [start, end) 中所有使谓词 p 为 true 的元素,返回一个指向被修剪的序列的最后一个元素迭代器。

注意, remove_if() 并 不会真正地移除 序列范围 [start, end) 中的元素,若在一个容器上应用 remove_if(),容器长度并不会改变(remove_if() 不可能仅通过迭代器改变容器的属性),所有的元素都还在容器中。实际做法是,remove_if() 将所有应该移除的元素都移动到了容器尾部并返回一个 分界迭代器。“移除” 的所有元素仍可通过返回的分界迭代器访问到。

为实际移除元素,必须再对容器调用 erase() 以 “擦除” 元素,这也是 erase-remove idiom 名称的由来:

container.erase(remove_if(container.begin(), container.end(), pred), container.end());

remove_if() 以线性时间运行。

remove_if() 不能用于关联容器,如 set<> 或 map<>。

remove_if() 类似于 partition(),但有所差异:

partition()接受一个谓词,对容器元素进行划分,使得谓词为 true 的值排在容器的前半部分,谓词为 false 的值排在后半部分,返回一个迭代器,指向最后一个使谓词为 true 的元素之后的位置。

 控制台交互:

Hello world my dear lady. 666. you are my entire world.
^Z
666 : 1
are : 1
dear : 1
entire : 1
hello : 1
lady : 1
my : 2
world : 2
you : 1
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值