(讲课课件)优先队列与搜索
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
STL中的队列
对于STL队列在这里我们做一些简要的概述: 在使用STL队列的时候需要包含头文件”queue”,同时还要 使用命名空间std。可以用下面的这种方式定义一个队列:
queue<数据类型>队列的名称
queue的成员函数: 1、front() : 用来查看队头元素
//可以为任意类型
2、back() : 用来查看队尾元素
一、优先队列
优先队列知识要点
一、什么是优先队列: 优先队列是这样的一种数据结构,无论在任何时候,总是保 证处于队头的元素拥有最高的的优先级。那么可以确定对于优先 队列来说,我们只需关心其队头元素的情况。 二、优先队列的相关操作 1、创建一个空的优先队列 *2、将优先队列清空 3、判断优先队列是否为空 4、查看队首元素 5、将队首元素出队 6、将一个元素入队
这种实现的缺点就像前面所说的那样,如果数组大小不够的 话可能会出现很多问题。如果在程序事件要求比较严格的情况下, 这种队列实现方式还是值得一试的。前提是 程序员要把握好数组 的大小。
深度优先搜索与广度优先搜素算法描述
深度优先搜索:
*1、建立一个图 2、从一个指定的节点开始,按照一定的顺序所搜
3、对于每个找到的节点重复步骤2,如果节点被访问过,那么不能再 次访问,直到这个节点找不到其它相连的节点为止,整个搜索的过 程直到达到某种目的,或者遍历完所有的可达路径为止。
除此之外,要避免搜索已经搜过的元素,否则也会造成搜索 无法结束而带来的爆栈或者是超时问题。
广度优先搜索难点、关键
广度优先搜索的难点: 广度优先搜索需要借助队列来实现,那么首先我们要有一个 靠谱的队列实现。通常我们直接使用一个数组来实现队列,但是 如果数组的长度不是很长并且伴有多次出入队的操作就可能会出 现一些问题,比如新加入队列的元素引用了不合法的内存地址。 我们可以利用求模的方法来解决这些问题,但是如果实现的数组 长度过小仍会出现新加入的数据将原有队列中数据覆盖的情况。 如果不能确认自己写的队列是没有问题的,那么建议使用STL中 的队列。 同时要注意做好对访问过的点进行处理,如果访问过的点再 次加入搜索的队列中,那么会造成搜索永远不会结束的问题。因 此对于访问过的元素要做好标记工作。
优先队列运用注意事项
1、如果我们仅仅是关心每次使用的元素是所有元素中的拥有最值 的元素,而且不关心其它元素,那么这个时候可以使用涌现队列。 2、建议使用STL中的优先队列,因为这个优先队列实现是绝对靠 谱的。避免了自己用二叉堆实现优先队列时可能出现的错误。 3、如果选择使用STL实现优先队列,建议对运算符的重载知识进 行一下简单的复习,我们需要重载大于或者小于号来实现对多优 先级的元素进行优先级比较。
二、搜索问题
搜索知识要点
通常我们说的搜索是一种基于图的遍历方法,搜索可以大致 分为两种,一种是深度优先搜索,另一种是广度优先搜索。这两 种搜索方式是非常基础的,但也是ACM中最常见的。在以后的面试 或者是工作中仍然会见到它们,所以对于搜索的学习至关重要。 深度优先搜索:
对于深度优先搜索,我们一次要搜索一条路径,直到找到一条符合我 们要求的路径为止。深度优先搜索的是逐条路径的遍历,因此深度优先搜 索需要依赖系统栈。
priority_queue的成员函数:
empty() 判断队列是否为空 pop() 删除对顶元素 push(数据类型) 加入一个元素
size() 返回优先队列中拥有的元素个数
top() 返回优先队列队头元素
优先队列算法描述II
二叉堆的优先队列:
用二叉堆实现一个优先队列,我们只需要去维护一个最大堆即可: 1、建立一个n个节点的完全二叉树
优先队列算法描述I
STL中的优先队列:
由于STL中实现的优先队列效率不错,而且使用比较简单,这里就简单的介绍一 下有关STL中优先队列的使用。 想要正确的使用STL中的优先队列,那么必须包含头文件”queue”,而且是需要 使用命名空间”std”。很显然,只有C++中才能使用STL。因此如果想要使用STL中的优 先队列,就需要使用C++的编译器来编译代码。 定义一个优先队列容器: priority_queue<数据类型>优先队列名称; 这里的数据类型可以是任何的一种数据类型,包括结构体和类。由于一个自定义 的数据结构可能不仅仅有一个数据成员,因此比较优先级的时候可能涉及到多个数据成 员的比较,此时我们可以通过重载运算符来实现比较。
3、push(数据类型) : 将一个元素插入队列 4、pop() : 将一队头元素出队
5、empty() :判断队列是否为空
6、size() : 返回队列中元素的个数
用数组实现的队列
通常情况下也可以使用数组来实现队列,用数组实现的队列 运行速度上 要比STL中的队列要快。因为STL中的各种容器都涉 及到复杂的重载和出错处理问题,所以速度相对较慢。 用数组实现队列的方法很简单,首先定义一个足够大的数组 queue。接下来定义两个变量head和 tail,分别 表示 队列的队头 和队尾。如果我们想要向队列中插入一个元素,只需要将元素的 值赋给queue[tail],之后tail的值加一即可,如果想要将队头出队 也只需要将head的值加一 就可以了。
广度优先搜索:
*1、建立一个图 2、将起始节点加入到队列,并开始寻找与其相连的节点,如果节点 被加入过队列,那么就不能再加入一次了。将这些节点加入队列。 之后将队头元素出队。 3、每次对于队头元素,总是去寻找与之相连的节点,并将这些节点 加入队列, 之后将队头元素出队。 4、搜索结束的条件是队列为空,或者是达到了某个预计的条件。
优先队列举例2
如何才能得到更多的雪人(HRBUST 1176) 东北的冬季,尤其是过年的时候,小陈老师喜欢去堆雪人。 每个雪人主要由三个雪球构成:大雪球、中雪球、小雪球。 他已经准备好了N个雪球,半径分别等于r1, r2, ..., rn。如果要堆 一个雪人,就需要三个半径互不相等的雪球。 例如: 三个雪球的半径为1、2、3,能够用来堆一个雪人。但是半径为2、 2、3或者2、2、2的三个雪球就不可以。 快帮帮小陈老师,算算他最多能用这些雪球堆多少个雪人。
建图函数样例:
void build(int st, int ed, value_t val) { edge[pot].start = st; edge[pot].end = ed; edge[pot].val = val; edge[pot].next = head[st]; head[st] = pot++; } 注意 ,这里我们假定已经定义了全局的Edge数组edge: struct Edge edge[SIZE_ARRAY]; 并且定义了一个全局的 整型数组head和记录边个数的整型变量 pot
4、注意!如果你选择了使用GCC编译器,那么只能用二叉堆来 实现优先队列。不建议在比赛中这么做。
*5、使用STL中的优先队列,当每组测试数据结束后,不要忘记 将优先队列清空。(STL的优先队列没有清空函数)
优先队列举例1
系统的进程调度问题(HRBUST 1246) 在一个操作系统中总是会有很多的进程在运行,当然,这些 进程是有优先级的。系统需要知道那些进程是需要马上被执行的。 通常来说优先级高的进程会被先执行。我们有的时候需要知道系 统中哪个进程会被最先执行。这仅仅是通过进程优先级判断是不 够的,因为在优先级相同的时候应该按照进程调用的时间进行比 较具体哪个进程应该先被执行。 与之类似的就是windows的消息队列问题,两个问题的本质 是完全一样的,消息队列的优先级情况与上面的例子是完全一样 的。
广度优先搜索:
广度优先搜索时,我们先要寻找当前节点可以直接遍历到的所有节点 (即直接连通的节点),并将这些节点加入一个队列。每次我们将队头元 素做出相同的操作,之后将队头元素出队。直到找到目标为止。
*使用邻接表建图
数据结构-图:
图是由一定数量的点和 连接这些点的 边构成的一种数据结构,通常我们构建图 都是会通过构建边来完成的,下面我们就来看看一条边应该包含什么样的信息: 1、确定一条边的两个端点start和end,但是往往在有向图中只需知道一个固定 的端点即可(例如可以只知道每条边的终点) 2、边的权值,权值是非常重要的,可能会影响到很多东西。 3、指向下一条以这条边起点为起始点边的指针,但是通常这个不会用指针来实 现,因为指针的使用相对比较麻烦。 4、还需要一个额外的变量记录图中一共有多少条边,这个变量一般是 全局的 5、需要一个全局的整型数组head作为辅助
ACM暑假培训
优先队列与搜索
主讲:杨和禹 指导:陈禹 哈尔滨理工大学ACM集训队
பைடு நூலகம்
参考学习资料
图书:
*《算法导论》(第二版) ,Thomas H.Cormen、Charles E.Leiderson、Ronald L.Rivest、Clifford Stein著、机械工业出版社,2010-11-01
优先队列难点、关键
优先队列的实现: 1、暴力方式实现 2、二叉堆实现 *3、斐波那契队实现 4、STL中的优先队列
各种实现方式的优缺点:
1、暴力方法实现:实现方式简单,但是很多操作实现的时间代价过大,不适合 相对较大数据的处理。 2、二叉堆实现:实现相对复杂,需要数据结构相关知识,但是很多操作的时间 代价相对较小,总体来看时间消耗让人满意。 3、斐波那契堆实现:实现更加的复杂,需要有扎实的数据结构与算法功底,但 是相对二叉堆实现可以提供更好的期望时间复杂度。 4、STL中的优先队列:可以理解为一种现成的优先队列模板,STL的实现的时 间消耗相对比较理想。由于STL库已经经过很多检验,因此STL还是相当靠谱的。
《算法竞赛入门经典》,刘汝佳编著,清华大学出版社,2009-11-01
《算法竞赛入门经典训练指南》,刘汝佳、陈锋编著,清华大学出版社,2012-10-01
练习题:
HRBUST :
1246 1176 1012 1143 1490 POJ: 1088 深度优先搜索 优先队列 优先队列 广度优先搜素 深度优先搜索或广度优先搜索 深度优先搜索(需要用邻接表或者邻接矩阵建图)
一个可供参考的自定义数据结构Edge:
struct Edge { int start; int end; int next; value_t val; };
/*通常我们建图的过程中总是将点映射为一个整数*/ /*用一个整数next来替代指针,需要配合head数组使用*/ /*value_t是自定义的数据类型,val可能是任何的数据类型*/
2、遍历标号为n/2到1的所有节点,每次比较节点与其字节点的优先 级大小,并 将其中优先级最大的节点交换到该节点处。完成遍历 之后,堆顶元素的优先 级一定是最高的。
3、若要取出优先队列的对头元素,我们需要保持堆顶元素的值,并 且将堆顶元 素与堆中标号最大的元素位置互换,接下来维护堆最 大堆的性质,这样仍符 合优先队列的特点。 4、如果一个优先队列具有多重优先级,那么需要写一个比较函数, 通过比较函 数才能够判断优先级。 5、如果想要清空一个优先队列,那么我们只需要标记二叉堆中的元 素个数为,并不需要实际去清除这个元素(用静态数组)。 6、向优先队列中插入一个元素,应该先将其标记为堆中标号最大的 元素,之后 不断何其父节点比较优先级并交换,直到这个元素处 于一个合适的位置。
深度优先搜索难点、关键
深度优先搜索的难点: 深度优先搜索的实现依赖于递归的思想和系统栈,深度优先 搜索中最容易出现的问题爆栈而导致的程序崩溃(系统允许一个 程序在栈区使用的内存大小有限),或者是由于占用内存过大而 导致评测系统返回MLE,也有可能是由于递归层数过深导致了 TLE。因此我们需要一个有效的方法来防止上述现象的发生。 为了防止上述问题的发生,应该首先明确的一个问题就是递 归的结束条件。如果这个问题没有明确的话,那么写出来的递归 函数多半是会出现问题的。这种情况下,函数的递归往往会没有 一个结束点,一次会导致爆栈。所以在设计递归函数的时候要严 格的检查函数的递归结束点设置是否正确,这个递归结束点是否 是可以到达的。