C++Primer习题第十一章

习题11.1:描述map和vector的不同。

答:(标准)

两种容器的根本差别是:

顺序容器中的元素是按“顺序”存储的。对于vector这样的顺序容器,每个元素有唯一对应的位置编号。所有的操作都是按位置(编号)进行的。

而map这种关联容器,是为了高效实现“按值访问元素”这类操作而设计的。容器中的元素是按关键字值来存储的,关键字值和元素数据建立起对应关系。底层数据结构是红黑树,哈希表等,可高效地实现按关键字查找 添加 删除元素等操作。


习题11.2:分别找出最适合使用list vector deque map 和 set 的例子。

(标准)

答:

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

如果元素较大,数量预先不知道,或是程序运行过程中频繁变化,对元素的访问更多是顺序访问全部或者很多元素,则list很适合。

map很适合对一些对象按他们的某个特征进行访问的情形。典型的例如按学生的名字来查询学生信息,即可将学生名字作为关键字,将学生信息作为元素值,保存在map中。

set就是集合类型。当需要保存特定的值的集合——通常是满足或不满足某种要求的集合,用set最为方便。


习题11.3:编写你自己的单词计数程序。

#include
#include
#include
#include
using namespace std;

int main()
{
    ifstream fin;
    fin.open( "T9_49_data.txt" );
    if( !fin ){
        cerr << "打开文件失败!";
        return -1;
    }
    string word;
    map word_count;
    while( fin >> word )
        ++word_count[ word ];
    fin.close();

    for( const auto &obj : word_count )
        cout << obj.first << " occurs " << obj.second
            << (( obj.second > 1 )? " times" : " time" ) << endl;

    return 0;
}


练习11.4:扩展你的程序,忽略大小写和标点,比如,"example."、"example,"和"Example"应该递增相同的计数器。
#include
#include
#include
#include
#include

using namespace std;

int main()
{
    ifstream fin;
    fin.open( "T9_49_data.txt" );
    if( !fin ){
        cerr << "打开文件失败!";
        return -1;
    }
    string word;
    map word_count;
    while( fin >> word ){
        auto iter = word.begin();
        while( iter != word.end() ){
            if( ispunct( *iter ) )
                iter = word.erase( iter );
            else{
                *iter = tolower( *iter );
                ++iter;
            }
        }

        ++word_count[ word ];
    }
    fin.close();

    for( const auto &obj : word_count )
        cout << obj.first << " occurs " << obj.second
            << (( obj.second > 1 )? " times" : " time" ) << endl;

    return 0;
}


练习11.5:解释map和set的区别。你如何选择使用哪个?

当需要查找给定值对应的数据时,应使用map

如果只需判定给定值是否存在时,应使用set


练习11.6:解释set和list的差别。

如果只需要顺序访问这些元素,或者是按位置访问这些元素,使用list

如果需要快速判定是否有元素等于给定 值,则应该使用set


练习11.7:定义一个map,关键字是家庭的姓,值是一个vector,保存家庭中孩子(们)的名。编写代码,实现向新的家庭以及向已有家庭添加新的孩子。

#include
#include
#include
#include
#include
#include

using namespace std;

int main()
{
    map> family;
    string line, familyName, kidName;
    ifstream fin;
    fin.open( "T11_7_data.txt" );
    if( !fin ){
        cerr << "打开文件失败!";
        return -1;
    }
    while( getline( fin, line ) ){
        istringstream in( line );
        in >> familyName;  //读取家庭的姓
        while( in >> kidName )
            family[ familyName ].push_back( kidName );
    }
    fin.close();

    for( const auto &f : family ){
        cout << f.first << ":" << endl;
        for( const auto &kid : f.second )
            cout << kid << endl;
        cout << "\n";
    }

    return 0;
}




练习11.8:编写一个程序,在一个vector而不是一个set中保存不重复的单词。使用set的优点是什么?

#include
#include
#include
#include

using namespace std;

int main()
{
    vector svec;
    string word;
    ifstream fin;
    fin.open( "T10_9_data.txt" );
    if( !fin ){
        cerr << "打开文件失败!";
        return -1;
    }

    while( fin >> word ){
        auto iter = find( svec.cbegin(), svec.cend(), word );
        if( iter == svec.cend() )//word不在vector中
            svec.push_back( word );
        else
            continue;
    }
    fin.close();

    sort( svec.begin(), svec.end() );
    cout << "容器中的元素为:" << endl;
    for( const auto &s : svec )
        cout << s << " ";
    cout << endl;

    return 0;
}


#include
#include
#include

using namespace std;

int main()
{
    set uni_gather;
    string word;
    ifstream fin;
    fin.open( "T10_9_data.txt" );
    if( !fin ){
        cerr << "打开文件失败!";
        return -1;
    }

    while( fin >> word )
        uni_gather.insert( word );
    fin.close();

    cout << "容器中的元素为:" << endl;
    for( const auto &s : uni_gather )
        cout << s << " ";
    cout << endl;

    return 0;
}

使用set,检查是否重复的工作是由set模板负责的,程序员无须编写对应的代码,程序更简洁。更深层次的差别,vector是无序线性表,find查找指定值只能采用顺序查找方式,所花费的时间是与vector.size()的大小成线性的。

而set是红黑树实现的,花费时间与size的大小成对数关系。当单词数量非常多,set的优势尤其明显。


练习11.9:定义一个map,将单词与一个行号的list关联,list中保存的是单词所出现的行号。

#include
#include
#include
#include
#include
#include
#include

using namespace std;

int main()
{
    ifstream fin;
    fin.open( "T11_9_data.txt" );
    if( !fin ){
        cerr << "打开文件失败!";
        return -1;
    }
    string word, line;
    int lineNo = 0;
    map> word_lineNo;
    while( getline( fin, line ) ){
        ++lineNo;
        istringstream sin( line );
        while( sin >> word ){
            auto iter = word.begin();
            while( iter != word.end() ){
                if( ispunct( *iter ) )
                    iter = word.erase( iter );
                else{
                    *iter = tolower( *iter );
                    ++iter;
                }
            }
            word_lineNo[ word ].push_back( lineNo );
        }

    }

    fin.close();

    for( const auto &obj : word_lineNo ){
        cout << obj.first << " occurs in line:" ;
        for( const auto &i : obj.second )
            cout << i << " ";
        cout << endl;
    }
    return 0;
}



练习11.10:可以定义一个vector::iterator 到 int 的 map 吗?list::iterator 到 int 的 map 呢?对于两种情况如果不能,解释为什么?
(标准)

答:

由于有序容器要求关键字类型必须支持比较操作 < ,因此:

前者可以,后者不行。


练习11.11:不适用decltype重新定义bookstore 。

我想到了可以使用类型别名。

typedef bool (*pf) ( const Sales_data &lhs, const Sales_data &rhs );

multiset bookstore( compareIsbn );


练习11.12:编写程序,读入string和int的序列,将每个string和int存入一个pair中,pair保存在一个vector中。 

略了。


练习11.13:上一题的程序中,至少有三种创建pair的方法。编写此程序的三个版本分别采用不同的方法创建pair。解释你认为哪种形式更易于编写和理解?为什么?

列表初始化的方式更简洁和容易理解。


练习11.14:扩展你在11.2.1节练习中编写的孩子姓到名的map,添加一个pair的vector,保存孩子的名和生日。
这次简洁起见,就不通过读入文件或者从标准输入读取了。直接自己在程序中添加。

#include
#include
#include
#include
#include

using namespace std;
void add_child( map>> &families,
               const string &familyName, const string &kidName, const string &birthday );

int main()
{
    map>> families;
    add_child( families, "吴", "悦", "1996-06-27" );
    add_child( families, "林", "凯东", "1996-08-20" );
    add_child( families, "吴", "所谓", "1995-04-29" );
    add_child( families, "付", "一招", "1995-12-13" );
    add_child( families, "林", "平之", "1524-3-22" );

    for( const auto &f : families ){
        cout << f.first << ":" << endl;
        for( const auto &kid : f.second )
            cout << kid.first << " 的生日: " << kid.second << endl;
        cout << "\n";
    }

    return 0;
}

void add_child( map>> &families,
               const string &familyName, const string &kidName, const string &birthday )
{
    families[ familyName ].push_back( { kidName, birthday } );
}


练习11.15:对一个int到vector的map,其mapped_type 、key_type 和 value_type分别是什么?

mapped_type 是 vector

key_type是int

value_type是pair>


练习11.16:使用一个map迭代器编写一个表达式,将一个值赋予一个元素。

略了。

值得注意的是不能修改关键字的值,只能修改其对应的值。


练习11.17:假定c是一个string的multiset,v是一个string的vector,解释下面的调用。指出每个调用是否合法。

set的迭代器是const的。因此只允许访问set中的元素,而不能改变set。

所以前两个调用非法。

后两个调用合法。


练习11.18:写出第382页循环中map_it的类型,不要使用auto或decltype 。

map::iterator


练习11.19:定义一个变量,通过对11.2.2节中的名为bookstore的multiset调用begin()来初始化这个变量。写出变量的类型,不要使用auto或decltype。

typedef bool (*pf) ( const Sales_data &, const Sales_data & );

multiset bookstore( compareIsbn );

multiset::iterator it = bookstore.begin();


练习11.20:重写11.1节练习的单词计数程序,使用insert代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。

#include
#include
#include
#include
#include

using namespace std;

int main()
{
    ifstream fin;
    fin.open( "T9_49_data.txt" );
    if( !fin ){
        cerr << "打开文件失败!";
        return -1;
    }
    string word;
    map word_count;
    while( fin >> word ){
        auto iter = word.begin();
        while( iter != word.end() ){
            if( ispunct( *iter ) )
                iter = word.erase( iter );
            else{
                *iter = tolower( *iter );
                ++iter;
            }
        }
        auto ret = word_count.insert( { word, 1} ); //返回一个pair。若插入成功返回,second成员返回true;否则false
        //注意:ret的first成员是指向具有给定关键字的元素的迭代器。需要解引用才能提取map的元素,所以要用->
        if( !ret.second )
            ++ret.first->second;
    }
    fin.close();

    for( const auto &obj : word_count )
        cout << obj.first << " occurs " << obj.second
            << (( obj.second > 1 )? " times" : " time" ) << endl;

    return 0;
}

显然,使用下标的版本更加简洁易懂。



练习11.21:假定word_count是一个string 到 size_t 的map ,word是一个string , 解释下面循环的作用。

while( cin >> word )

++word_count.insert( { word, 0 } ).first->second;

对每个从cin读入的word进行操作,先向word_count插入关键字word及对应的值0;然后返回迭代器,通过迭代器的first成员指向关键字word的元素,解引用迭代器,对给定关键字的值进行前置递增。


练习11.22:给定一个map>, 对此容器插入一个元素的insert版本,写出其参数类型和返回类型。

参数类型 pair>

返回类型pair>::iterator, bool>


练习11.23: 11.2.1节练习中的map以孩子的姓为关键字,保存它们的名的vector,用multimap重写此map。

#include
#include
#include
#include
#include
#include

using namespace std;

int main()
{
    multimap> family;
    string line, familyName, kidName;
    ifstream fin;
    fin.open( "T11_7_data.txt" );
    if( !fin ){
        cerr << "打开文件失败!";
        return -1;
    }
    while( getline( fin, line ) ){
        istringstream in( line );
        vector kidNames;
        in >> familyName;  //读取家庭的姓
        while( in >> kidName )
            kidNames.push_back( kidName );
        family.insert( {familyName, kidNames } );
    }
    fin.close();

    for( const auto &f : family ){
        cout << f.first << ":" << endl;
        for( const auto &kid : f.second )
            cout << kid << endl;
        cout << "\n";
    }

    return 0;
}

练习11.24:下面的程序完成什么功能?

map m;

m[0] = 1;

先创建一个关键字为0,值为0的pair类元素。然后将该关键字的对应的值赋值为1。


练习11.25:对比下面程序与上一题的程序。

vector v;

v[0] = 1;

v是一个空的容器,用下标访问它的非法的位置0的元素会引发错误造成系统崩溃。

 

练习11.26:可以用什么类型来对一个map进行下标操作?下标运算符返回的类型是什么?请给出一个例子——即,定义一个map,然后写出一个可以用来对map进行下标操作的类型以及下标运算符将会返回的类型。

PS:通常,解引用一个迭代器所返回的类型与下标运算符返回的类型是一样的。但对map则不然。

解引用一个迭代器会得到一个value_type对象,而下标运算符会得到一个mapped_type对象。

而对map进行下标操作,应使用value_type类型。


练习11.27:对于什么问题你会使用count来解决?什么时候是又会选择find呢?

find查找关键字在容器中出现的位置,而count则还会统计关键字出现的次数。

当我们只关心关键字是否在容器中时,使用find就足够了。特别是不允许重复关键字的关联容器,find和count的效果没什么区别,使用find即可。


练习11.28:对一个string到 int的vector 的map,定义并初始化一个变量来保存在其上调用find所返回的结果。

find返回一个迭代器。

变量的定义及初始化:

map>::iterator iter;


练习11.29:如果给定的关键字不在容器中,upper_bound、lower_bound和equal_range分别会返回什么?

uppper_bound返回一个迭代器,指向第一个大于给定的关键字的元素。若关键字不在容器中,返回尾后迭代器。

lower_bound返回一个迭代器,指向第一个不小于给定关键字的元素(第一个安全插入点)。若不在容器中,返回尾后迭代器。

equal_range返回一个迭代器pair,表示给定关键字的范围。若不存在给定关键字,则pair的两个成员都等于尾后迭代器。


练习11.30:对于本节的最后一个程序中的输出表达式,解释运算对象pos.first->second的含义。

pos是一个迭代器的pair,pos.first是一个指向第一个具有给定关键字的元素的迭代器。

对其使用->second,解引用得到一个pair,再访问它的second的成员。

按照题意:

pos.first得到一个指向给定作者名字的pair。

->解引用得到该pair,它的first成员是作者的名字,second成员是作者的著作名字。


练习11.31:编写程序,定义一个作者及其作品的multimap。使用find在multimap中查找一个元素并erase删除它。确保你的程序在元素不在map中时也能正常运行。

#include
#include
#include
#include
#include
#include

using namespace std;

void FindAndErase( multimap> &family, const string &key );

int main()
{
    multimap> family;
    string line, familyName, kidName;
    ifstream fin;
    fin.open( "T11_31_data.txt" );
    if( !fin ){
        cerr << "打开文件失败!";
        return -1;
    }
    while( getline( fin, line ) ){
        istringstream in( line );
        vector kidNames;
        in >> familyName;  //读取家庭的姓
        while( in >> kidName )
            kidNames.push_back( kidName );
        family.insert( {familyName, kidNames } );
    }
    fin.close();

    cout << "原始数据:" << endl;
    for( const auto &f : family ){
        cout << f.first << ":" << endl;
        for( const auto &kid : f.second )
            cout << kid << endl;
        cout << "\n";
    }
    cout << "\n\n";
    FindAndErase( family, "LeBron" );  //Lebron是其中的一个关键字
    FindAndErase( family, "Greta" );  //Greta不属于容器中的关键字
    cout << "删除某些数据后...:" << endl;
    for( const auto &f : family ){
        cout << f.first << ":" << endl;
        for( const auto &kid : f.second )
            cout << kid << endl;
        cout << "\n";
    }
    return 0;
}



void FindAndErase( multimap> &family, const string &key )
{
    auto iter = family.find( key );

    if( iter != family.end() )
        family.erase( iter );
    else;
}

练习11.32:使用上一题定义的multimap编写一个程序,按字典顺序打印作者列表和他们的作品。

在multimap中,作者的名字本来就已经默认按照字典顺序排序。

难点是:使得相同的作者(相同关键值)的作品名字也按字典顺序输出。也就是在插入进关联容器时,就已经按关键字及其键值排好序。

如果是map还好办,使用map>存储数据。最后对map的所有vector调用sort排序再输出。

但是现在是要使用multimap

我的思路是:自定义这个multimap的比较函数,但是遇到困难:这个函数应该是接受两个const string&,也就是multimap 的两个元素各自的关键字。那么我如何通过这个两个关键字,来比较这两个关键字映射的值呢?这里没想出来。

另一个想法是:照常向multimap添加元素。添加完后,遍历每个元素,对每个相同关键字对应的值存储到一个vector中进行sort排序再输出。这种方法不难实现但是有点舍近求远。个人不是很喜欢这种做法。

这题先搁置在这儿吧,如果有大神可以跟我说说你们的想法吗?


练习11.33:实现你自己版本的单词转换程序。

先把要求写在下面捋捋思路:

功能:给定一个string,将它转换成另一个string。

输入两个文件。第一个文件是:转换规则。第二个文件:用来转换的文本。

转换规则格式:第一个单词是被替换的string,这一行后面的所有单词替换第一个单词。


为了简单起见,我的转换规则和待转换文本和课本的相同。

#include
#include
#include
#include
#include

using namespace std;

map buildMap( ifstream &rule );
const string& trans( const string &s, const map &trans_map );
void word_transform( ifstream &rule, ifstream &input );


int main()
{
    ifstream fin_rule( "T11_33_rule.txt" );
    ifstream fin_input( "T11_33_input.txt" );
    if( !fin_rule || !fin_input ){
        cerr << "打开文件失败";
        return -1;
    }
    word_transform( fin_rule, fin_input );
    fin_rule.close();
    fin_input.close();

    return 0;
}

void word_transform( ifstream &rule, ifstream &input )
{
    auto trans_map = buildMap( rule );
    string text;

    while( getline( input, text ) ){
        istringstream in( text );
        string word;
        bool firstWord = true;
        while( in >> word ){
            if( firstWord )
                firstWord = 0;
            else
                cout << " ";
            cout << trans( word, trans_map );
        }
        cout << endl;
    }
}


const string& trans( const string &s, const map &trans_map )
{
    auto iter = trans_map.find( s );
    if( iter != trans_map.end() )
        return iter->second;
    else
        return s;
}
map buildMap( ifstream &rule )
{
    map trans_map;
    string line, key, value;
    while( getline( rule, line ) ){
        istringstream sin( line );
        sin >> key;
        while( sin >> value );
        if( value.size() > 1 )
            trans_map[ key ] = value;
        else
            throw runtime_error( "no rule for " + key );
    }
    return trans_map;
}

练习11.34:如果你将transform函数中的find替换为下标运算符,会发生什么情况?

如果关键字不在关联容器中,下标运算符会创建一个pair(进行值初始化),将其插入到容器中。对于单词转换程序,这可能会导致转换规则的改变,发生不在期望之内的错误转换。


练习11.35:在buildMap中,如果进行如下改写,会有什么效果?

trans_map[ key ] = value.substr( 1 );

改为 trans_map.insert( { key, value.substr( 1 ) } );

(1)第一种情况,

如果key在容器中,会将key原来对应的值改变为value.substr(1);

如果key不在容器中,则会构造一个关键字为key,值为默认初始化的pair插入到容器中,然后再将key对应的值改变为value.substr(1)。


(2)改写后的第二种情况:

如果key不在容器中,会构造一个关键字为key,值为value.substr(1) 的pair插入到容器中。

但如果key已经在容器中,insert则会什么都不做。


练习11.36:我们的程序并没有检查输入文件的合法性。特别是,它假定转换规则文件中的规则都是有意义的。如果文件中的某一行包含一个关键字、一个空格,然后就结束了,会发生什么?预测程序的行为并进行验证,再与你的程序进行比较。

书中的程序已经检查了输入文件的合法性。

如果出现上述的文件,会抛出一个异常。throw runtime_error( "no rule for " + key );


练习11.37:一个无序容器与其有序版本相比有何优势?有序版本有何优势?

(标准)

答:

无序版本通常性能更好,使用也更为简单。有序版本的优势是维护了关键字的序。

当元素的关键字类型没有明显的序关系,或是维护元素的序的代价非常高时,无序容器非常有用。

但当应用要求必须维护元素的序时,有序版本就是唯一的选择。


练习11.38:用unordered_map重写单词计数程序和单词转换程序。

两题都是直接把对应的map改成unoredered_map即可。

代码都不用贴了感觉。



你可能感兴趣的:(C++primer习题)