上一期是关于STL和并查集结合的例题,也附了STL中部分容器的使用摘要,由于是从网上东拼西凑的,感觉有的关键点还是没解释清楚,现在从其中摘出两个容器,用例题对它们的用法进行进一步解释。


以下是例题的介绍

题目简述:有一个人每天往返于一段道路中,走着走着就觉得无聊了,于是自己给自己找乐子发明了一个扔石头的游戏:在一次单程行走过程中,假设整段路程中有 n 块石头,每块石头有两个重要信息,一个是所处的位置,一个是石头可以扔出的距离。还有一点值得注意,石头的块头越大扔的距离就越近。此人从第一块石头开始,遇到的第奇数个石头就搬起来往前扔,遇到的第偶数个石头不作处理。如果在某个位置上有两个石头,那么此人会先看到个头较大的那块石头(即扔的距离较近的那块石头)。现在要求算出此人走出多远就没石头可扔了。

输入数据:第一行给出测试数据的组数 t ( 1< = t <= 10 ),接下来给出 t 组测试数据,每组数据第一行 给出测试数据的组数 n ( 0 < n <=10 000) ,接下来的 n 行,每行给出一对测试数据 p 和 d ,( p( 0 <= p <= 10 000) 表示石头的位置,d ( 0 <= d <= 1 000) 表示石头可扔出的距离 )

输出数据:给出每组测试数据的结果,每行一个数据。

测试数据如下:


现在对整个题目进行分析,此人从第一块石头出发,随着时间推移接下来遇到的石头相对于起始位置越来越远,因此石头的位置是一个递增的信息,由此可以断定,存储石头信息的结构应该具备顺序结构,但石头的位置并不连续,如果用 vector 来存储,势必会造成空间的大量浪费。此人遇到偶数个石头丢弃,遇到奇数个石头要朝着他行走的方向往前扔,也就意味着扔到前面的石头他还会再遇到。这就需要在处理的时候,删掉偶数块的石头,并将奇数块石头重新添加到序列其他的位置,这就要求存储石头信息的结构需要有优良的插入、删除性质,序列型容器 vector 在这一点上更显的能力不足,这个时候,考虑其他的序列容器 队列queue 、链表 list 。链表虽然在随机插入删除方面很有优势,但若要求有序,链表操作是相当麻烦的。这个时候就剩 queue 还没被抛弃,题目要求遇到一个石头处理一个,即将此石头出列进行其余操作,这正是队列具备的特征,处理石头的时候需要将奇数的石头根据位置信息插入到相应位置。在有序的序列中,将元素插入到相应位置,priority_queue 就是应此要求诞生的。

选定了存储结构,现在来讨论一下具体的实现:使用计数器对此人遇到过的石头进行计数,分成奇数和偶数情况来处理,偶数情况将该石头信息出队不作处理,奇数情况将石头出队,并把位置信息更改为当前位置加可扔距离再次入队。每次对当前访问的石头位置进行存储,如此处理直至队列为空,输出最后一个处理石头的位置信息即可。

优先队列需要对队列的优先标准进行定义:当位置不相同时,位置靠前的优先性高,位置相同时,可扔距离较近的优先性较高。

在这个问题中需要考虑可扔距离为零的特殊情况。

下面对优先队列使用中需要注意的问题进行阐述


优先队列包含在 queue 头文件中

优先队列的声明有三种形式,

第一种:

priority_queue< element_type >  que;

此时声明了一个存储 element_type 类型元素的优先队列 ,名称为 que,这句声明中包含了如下信息:

  1. 该优先队列元素的类型为 element_type,

  2. 该优先队列使用的容器为默认容器 vector ,

  3. 该优先队列使用的优先级关系为优先级由高到低,一般的数字大的数据优先级高,此时队首的元素为优先级高的元素。确定优先级的比较函数是less();请注意,优先队列中的内置函数只能对基本数据类型进行排序,如果要对特殊类型进行排序,需要自定义比较函数。

第二种:

priority_queue< element_type, name< element_type > > ;

此时声明了一个存储 element_type 类型的元素的优先队列 ,名称为 que,这句声明中包含了一下信息:

  1. 该优先队列元素的类型为 element_type ,

  2. 该优先队列使用的容器为 name 容器,( 此处的 name 为用户需要使用的容器的名称,并非真实存在的某容器,假设 此处需要使用 queue容器,那么声明为

    priority_queue< element_type, queue< element_type > >

  3. 该队列使用的优先级关系为默认优先级(由高到低),默认比较函数为 less()。

第三种:

priority_queue< element_type , vector< element_type > , less<element_type > > ;

这种定义用于用户使用非 less()函数的其他优先级。

需要注意此时无论容器是不是默认容器 vector 都需要进行传参,这由优先队列相关函数内部实现决定。

例如:

现在元素类型为结构体类型,比较函数需要改变,则 处理如下:

struct node

{

element_type x;

element_type y;

};

bool compare( node a, node b )

{

return a . x   >  a . y   ;(此处的优先级关系可任意更改)

}

priority_queue<node , vector<node> , compare<node> > ;

另例如下:

struct node

{

friend  bool operator < ( node a , node b )

{

return a . y  >  b . y ; (同样,此处的优先级顺序也是可以根据需要任意更改的)

}

element_type x;

element_type y;

};

priority_queue< node>

或者下面这种:

priority_queue< node , vector<node> , less<node> > ; // 此时优先级实际上已经更改。

补充说明:

  1. 在优先队列中,内置优先性比较函数有两个:

    less(),由队首开始优先性逐渐减小

    greater(),由队首开始优先性逐渐增大

    但不声明的情况下,默认使用 less(),需要使用 greater ()的时候,要使用上面介绍的第三种声明方式。

2. 优先队列其余函数大多与普通队列 queue 的使用方法类似。


以上是使用优先队列的方法,下面我将用 multimap 方法对此题进行讲解,在此题中,multimap  方法似乎有些麻烦,但是也不失为一种解题思路,重在介绍 multimap 的相关函数使用方法。

在序列式容器中有自动排序功能的是 priority_queue ,而关联式容器也有这个特点,而且红黑树实现效率非常高。

关联式容器有 set / multiset  和 map / multimap 这两对:

set 的结构相当于集合,除了满足集合的唯一性、确定性,还满足有序性(默认由小到大排列)。multiset 与 set 的相关用法大多相同,唯一的区别在于,multiset 允许集合中有相同元素的出现,由此会引起部分函数的差异。

map/multimap 的结构相当于 映射对的集合,同样,两者大同小异,区别在于multimap中允许有相同键值的元素出现。

由于在某一确定时刻每个石头的位置和可扔距离是确定的,且一一对应。因此我想到可以用 map 容器,又因为扔石头过程中,同一位置有可能出现多于一块石头,因此可以确定使用 multimap 容器。

思路和上面的思路大同小异,先将初始数据存入 map ,然后设置计数变量和存储当前位置的变量,map 不空就不断进行计数变量的奇偶判断和map 删除、插入元素的处理。同样应该考虑可扔距离为零的情况。

在这个过程中有一些需要注意的地方:

一、头文件

multimap 包含在 map 头文件中,使用时要在程序打开头文件编写时加入#include<map>语句。

二、声明

分为两种:

第一种:

multimap<key_type , value_type> mp;

包含以下信息

  1. 声明了一个 键值为 key_type 类型 、关联值为 value_type 类型的 multimap  型变量 mp;

  2. multimap内 的键值排序按照默认排序顺序由小到大;

第二种:

multimap<key_type , value_type ,compare< key_type> >;

包含以下信息

  1. 声明了一个键值为 key_type 类型 ,关联值为 value_type 类型的 multimap  型变量 mp;

  2. multimap 内键值的排序规则由比较函数 compare给出,请注意这里 compare()函数中的参数只有 键值的类型,而没有关联值的类型,原因是,multimap 内部的排序只根据键值大小进行排序,而对关联值的顺序没有相应的处理,由此可见, multimap 中同一键值元素的相对位置并不是由关联值的大小决定,而是由添加顺序决定。

三、普通map可以用 map[key]来对键值为 key 的元素的关联值进行操作,但 multimap 不包含此性质,因此向multimap中添加元素只能使用 insert()函数,同样也不能用mp[ key ]来访问某一元素。用法如下:

  1. mp . insert( pair< key_type ,value_type>( key ,value ) )

  2. mp . insert( make_pair ( key , value ) ) ;

其中,pair是一种模版类型,含有两个成员分别为 first 和 second。 两个成员的类型可以根据需要任意确定,可以是基本数据类型,也可以是用户自定义类型。pair 可以作为新的类型 进行函数传参和使用。声明如下:

pair< element_type , element_type >  p = ( element_1 , element_2);

pair< element_type , element_type >  p = make_pair ( element_1 , element_2 )

需要使用pair 的两个成员时如下:

p . first

p . second

巧用 pair,当某函数需要返回两个类型不同的值时,可以定义 pair 来返回。

四、如何确定键值相同而关联值不同的元素的个数,以及如何对它们进行访问:

  1. 确定个数使用函数

    mp . count ( key ) ;

  2. 逐个进行访问

    mp . equal_range ( key ) ;

    注意,此函数的返回值是两个迭代器,其中,第一个迭代器返回的是 该键值第一个元素的迭代器,第二个迭代器返回的的是 该键值最后一个元素访问结束即将跳转的下一个元素的迭代器 。因此需要使用模版 pair ,如下:

    typedef   multimap< key_type , value_type >:: iterator  iter;

    pair< iter , iter > p ;

    p = mp . equal_range ( key ) ;

    while( p . first  !=  p . second )

    {

    cout << p . first << endl ;

    p . first ++ ;

  3. }

在此应该注意思考一个问题:当multimap中只有一个元素的时候, p . second 此时的值应该与当前 mp . end ( ) 返回值相同,这时候,如果循环写成这样:

while( p . first  !=  p . second )

{

cout << p . first << endl ;

mp . insert ( make_pair( elem_1 , elem_ 2 )  ) ;

p . first ++ ;

}

在插入新的元素后,   mp . end ( ) 值变了,但 p . second 值还是原来的末尾,既不是现在的尾,也不是新加入的元素的位置,p .first 会一直自增寻找结束条件,但 p . second 目前已经失效了,会陷入死循环,并且这种错误一般不容易被发现 。因此在编写程序的过程中,应该尽量避免这种情况的发生。改进方法需要根据具体的要求来实现,在这里就不举例了。

五、特殊查找

mp . lower_bound( key ) 查找第一个键值不小于 key 的元素,返回其迭代器

mp . upper_bound( key ) 查找第一个键值比 key 大的元素,返回其迭代器

由上面注意事项中所提到的,同一键值的不同元素在multimap中的顺序并不是按照 value 值的大小决定,而是由输入顺序决定,因此,在本题中,需要将相同键值的元素摘出来存在vector中,对其进行排序再使用。本题中,multimap 比 priority_queue 复杂的地方就在此了。


本题的题解到此结束,主要还是利用这个例题来详细的解释一下 优先队列和 multimap 中 不同于基本容器的地方,其它未涉及到的知识大都与基本容器的使用方法相同。今天例题的代码详见http://paste.ubuntu.com/24358192/。

multimap 和priority_queue详解的更多相关文章

  1. 【STL】优先队列priority_queue详解+OpenJudge-4980拯救行动

    一.关于优先队列 队列(queue)这种东西广大OIer应该都不陌生,或者说,队列都不会你还学个卵啊(╯‵□′)╯︵┻━┻咳咳,通俗讲,队列是一种只允许从前端(队头)删除元素.从后端(队尾)插入元素的 ...

  2. priority_queue详解

    priority_queue是一个安排好的顺序存储的队列,队首是优先级最高的元素. Template<class T , class Container = vector<T> , ...

  3. C++ STL 优先队列 priority_queue 详解(转)

    转自https://blog.csdn.net/c20182030/article/details/70757660,感谢大佬. 优先队列 引入 优先队列是一种特殊的队列,在学习堆排序的时候就有所了解 ...

  4. 优先队列priority_queue详解

    转载链接

  5. 详解C++ STL priority_queue 容器

    详解C++ STL priority_queue 容器 本篇随笔简单介绍一下\(C++STL\)中\(priority_queue\)容器的使用方法和常见的使用技巧. priority_queue容器 ...

  6. STL priority_queue 常见用法详解

    <算法笔记>学习笔记 priority_queue 常见用法详解 //priority_queue又称优先队列,其底层时用堆来实现的. //在优先队列中,队首元素一定是当前队列中优先级最高 ...

  7. C++ STL详解

    C++ STL详解 转载自:http://www.cnblogs.com/shiyangxt/archive/2008/09/11/1289493.html 一.STL简介 STL(Standard ...

  8. STL之heap与优先级队列Priority Queue详解

    一.heap heap并不属于STL容器组件,它分为 max heap 和min heap,在缺省情况下,max-heap是优先队列(priority queue)的底层实现机制.而这个实现机制中的m ...

  9. 7-set用法详解

    C++中set用法详解 转载 http://blog.csdn.net/yas12345678/article/details/52601454 C++ / set 更详细见:http://www.c ...

随机推荐

  1. IT类非开发面试总结--2

    面试总结.. ================================= 第一部分..(自己对公司的最低要求) 面试需要提前准备的是:<想好自己以后要干嘛--我在想我以后要干嘛> ...

  2. 2017-04-21周Java学习笔记

    2017-04-21-周Java学习笔记... -------------------------------------- 计算机起源于:战争中的炮弹轨道计算.Unix操作系统是使用C语言编写的操作 ...

  3. 九天学会Java,第二天,算术运算

    算术运算 先回顾上次我们提到的编程特性 变量和数据类型,赋值和输出 算术运算 选择结构 循环结构 函数定义,函数调用 变量作用域 栈,程序运行的基石 面向对象 异常处理 语言提供的公用包 第一天我们讲 ...

  4. 使用adb shell启动特定activity

    使用adb shell启动特定activity Android笔记 使用adb shell可以直接运行某个activity,避免调试过程中修改Manifest文件. 1.在AndroidManifes ...

  5. java基础部分细节

    访问控制修饰符 Java中,可以使用访问控制符来保护对类.变量.方法和构造方法的访问.Java支持4种不同的访问权限. 默认的,也称为 default,在同一包内可见,不使用任何修饰符. 私有的,以  ...

  6. css预处理器之一---sass(一)

    慕课学习笔记: CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 CSS 增加了一些编程的特性,将 CSS 作为目标生成文件,然后开发者就只要使用这种语言进行编码工作. 通俗 ...

  7. JDK+Apache+Tomcat+MySQL配置 一起来学习吧

    配置JDK1.8+Apache2.4+Tomcat8.0+mySQL5.1,网上的资料非常繁杂,花费几天时间配置成功,汇总记录. 操作系统:CentOS6.5 预先下载最新版软件: apache-to ...

  8. SessionStateMode之SQL Server共享session

    分布式应用首先要解决的是跨域的问题,解决session.frame.cookie的跨域是最基本的,然后才是负载均衡和性能优化,上面的不解决就没法往后面进行.上一博客主要是解决了frame跨域的问题,今 ...

  9. poj 1948二维01背包

    题意:给出不多于40个小棍的长度,求出用所有小棍组成的三角形的最大面积. 思路:三角形3边求面积,海伦公式:p=(a+b+c)/2;S=p*(p-a)*(p-b)*(p-c);因为最大周长为1600  ...

  10. 详解 mpls vpn 的实现

    MPLS VPN的实现 一.实验目的 该实验通过MPLS VPN的数据配置,使学生掌握路由器相关接口的IP地址设置.路由协议的配置以及MPLS VPN的完整的创建过程, 从而加深对IP网络的IP编址. ...