详解RMQ LCA
第一节 RMQ、LCA概述
LCA:Lowest Common Ancestor,译为最近公共祖先。其解释就是说:在有根树中,找出树中任意两个节点最近的公共祖先,或者说找到任意两个节点离树根最远的公共祖先。
RMQ:Range Minimum Query,译为区间最小值查询。其解释就是说:对于含有N个元素的数列A,在数列中找到两个指定索引之间的最小值及最小值的位置。
第二节 RMQ Algorithm
首先我们来看RQM算法,我将会根据预处理和查询的速度介绍几种解决该问题的方法。
解法一:直接遍历区间
- int MaxNum = 0;
- for(i = 0; i < range; i++)
- {
- /**查找最大值**/
- if(array[i] > MaxNum)
- {
- MaxNum = array[i];
- }
- }
解法二:切割法
解法一中查询的速度为O(M),如果每次查询都这样的话,那真就成了龟速了。于是我们对解法一做了预处理,这就是该节要讲的:切割法。
首先,我们将序列分成sqrt(N)个部分,用数组M[sqrt(N) ]来表示每个部分中最小的值的下标,即这个最小数的位置。对于数组M,我们只需对原序列进行一次遍历就可以得到M。如下图所示:
接下来我们来求RMQ[2,7]。为了得到区间[2,7]的最小值,我们需比较A[2],A[M[1]],A[6],以及A[7],并得到他们中最小值的下标。
解法三:排序
解法二已经提到我们的目的是查得快,那么我们可对选择区间的这M个数据进行排序,然后就可以直接得到最小值。但是如果做排序的话,会有很大的缺陷。我们来看看。
- 快速排序
- int partition(int *array, int low, int high)
- {
- int key = array[high];
- int i = low;
- int j = high;
- while(i < j)
- {
- while(array[i] <= key && i < j)
- {
- i++;
- }
- array[j] = array[i];
- while(array[j] >= key && i < j)
- {
- j--;
- }
- array[i] = array[j];
- }
- array[i] = key;
- return i;
- }
- void quicksort(int *array, int low, int high)
- {
- int index;
- int i = low;
- int j = high;
- if(i < j)
- {
- index = partition(array, low, high);
- quicksort(array, low, index - 1);
- quicksort(array, index + 1, high);
- }
- }
排完序之后就可以直接得到最值了!
解法四:Sparse Table(ST) algorithm
ST算法是一种比较高效的在线处理RMQ问题的算法,所谓在线算法,是指每输入一个查询就会马上处理这个查询。ST算法首先会对序列做预处理,完成之后就可以对查询做回答了。
预处理:首先用维护一个数组M[N][LogN],M[i][j]的值是从原序列A的i位置开始,连续2j 个元素的最小值的下标,如下所示:
我们采用DP的思想将区间分成两部分,即M[i][j - 1]和M[i][2^(j - 1)]。现在我们只需比较这两个子区间就可以得到M[i][j]了。比较规则如下:
- void Proprocessing(int M[N][logN], int *A, int N)
- {
- int i, j;
- for(j = 1; (1 << j) < N; j++)
- {
- for(i = 0; (i + (1 << j) - 1) < N; i++)
- {
- if(A[ M[i][j - 1] ] < A[ M[i + (1 << (j - 1))][i - 1]])
- {
- M[i][j] = M[i][j - 1];
- }
- else
- {
- M[i][j] = A[ M[i + (1 << (j - 1))][i - 1]];
- }
- }
- }
- }
解法五:线段树
- void init_tree(int node, int low, int high, int *array, int *M)
- {
- /***node:表示线段树中的某个节点
- ****low :表示低索引
- ****high:表示高索引
- ****array:表示原数组
- ****M: 表示维护下标的数组
- ***/
- if(low == high) //为叶子节点
- {
- M[node] = low;
- }
- else
- {
- init_tree(2 * node, low, (low + high)/2, array, M);
- init_tree(2 * node + 1, (low + high)/2 + 1, high, array, M);
- if(array[ M[2 * node] ] <= array[ M[2 * node + 1] ]) //拿到较小值的下标
- {
- M[node] = M[2 * node];
- }
- else
- {
- M[node] = M[2 * node + 1];
- }
- }
- }
- int query(int node, int low, int high, int *a, int *b, int i, int j)
- {
- /***node:表示线段树中的某个节点
- ****low :表示低索引
- ****high:表示高索引
- ****array:表示原数组
- ****M: 表示维护下标的数组
- ****i, j:表示要查询的区间
- ***/
- int s, t;
- if(i > high || j < low)
- return -1;
- if(low >= i && high <= j)
- return b[node]; //返回最小值的下标
- s = query(2 * node, low, (low + high)/2, a, b, i, j);
- t = query(2 * node + 1, (low + high)/2 + 1, high, a, b, i, j);
- if(s == -1)
- return b[node] = t;
- if(t == -1)
- return b[node] = s;
- if(a[s] <= a[t])
- return b[node] = s;
- else
- return b[node] = t;
- }
第三节 LCA Algorithm
维护数组:P[N][LogN]:其中,P[i][j]表示树中i节点的第j个祖先。
如果在同一层,那么我们通过DP思想,不断地求LCA(p = P[p][j],q = P[q][j]),一旦 p = q就停止,因为此时p和q的父节点是一样的,也就是说我们找到了最近公共祖先。
如果不在同一层,如果p > q,也就是说p相对与q,p在树的更深层。此时,我们仍然通过DP思想来找到q与p的祖先在同一层的节点,即q = p_祖先。接下来就可按照在同一层的做法做了。
- void preprocessing(int *t, int n, int p[][max])
- {
- int i, j;
- for(i = 0; i < n; i++)
- p[i][0] = t[i];
- for(j = 1; (1 << j) <= n; j++)
- {
- for(i = 0; i < n; i++)
- {
- if(p[i][j - 1] != -1)
- p[i][j] = p[p[i][j - 1]][j - 1];
- }
- }
- }
接下来就是查询了,如下:
- int query(int *t, int *l, int s, int t, int n, int p[][max])
- {
- int tmp, lg, i;
- if(l[s] < l[t])
- {
- tmp = s;s = t;t = tmp;
- }
- for(lg = 1; (1 << lg) <= l[s]; lg++);
- for(i = lg; i >= 0; i--)
- {
- if((l[s] - (1 << i)) >= l[t])
- s = p[s][i];
- }
- if(s == t)
- return s;
- for(i = lg; i >= 0; i--)
- {
- if(p[s][i] != -1 && p[s][i] != p[t][i])
- {
- s = p[s][i];
- t = p[t][i];
- }
- }
- return t[s];
- }
上面说的LCA的这种算法应该是最容易想到的,预处理过程O(NLogN),查询O(LogN)。还有一种类似于RMQ分割法德算法,我先就不在这赘述了,以后有时间一定补上。
第四节 结束语
后续:本文后半部分拖得周期较长,因此写的比较匆忙。如果本文的内容有任何不妥之处,请指正!
详解RMQ LCA相关推荐
- 详解最近公共祖先(LCA)
看本博客前建议先看一下ST算法解决RMQ问题详解 一,LCA概念 最近公共祖先(Lowest Common Ancestors, LCA)指 有根树中 距离两个 节点最近的 公共祖先. 祖先指 从当前 ...
- 五分钟搞懂后缀数组!后缀数组解析以及应用(附详解代码)
为什么学后缀数组 后缀数组是一个比较强大的处理字符串的算法,是有关字符串的基础算法,所以必须掌握. 学会后缀自动机(SAM)就不用学后缀数组(SA)了?不,虽然SAM看起来更为强大和全面,但是有些SA ...
- 线段树扫描线求矩形周长详解
线段树扫描线求矩形周长详解 原创 wucstdio 最后发布于2018-04-24 16:12:09 阅读数 841 收藏 发布于2018-04-24 16:12:09 版权声明:本文为博主原创文章, ...
- [动图演示]Redis 持久化 RDB/AOF 详解与实践
Redis 是一个开源( BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.它支持的数据类型很丰富,如字符串.链表.集 合.以及散列等,并且还支持多种排序功能. 什么叫持 ...
- 激光雷达与自动驾驶详解
激光雷达与自动驾驶详解 参考文献链接 https://mp.weixin.qq.com/s/Gk4JJZapKHXZE2AjliR8_A https://mp.weixin.qq.com/s/8xkd ...
- Redis持久化RDB/AOF详解与实践
Redis 是一个开源( BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.它支持的数据类型很丰富,如字符串.链表.集 合.以及散列等,并且还支持多种排序功能. 什么叫持 ...
- [动图演示]Redis 持久化 RDB/AOF 详解与实践 1
Redis 是一个开源( BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.它支持的数据类型很丰富,如字符串.链表.集 合.以及散列等,并且还支持多种排序功能. 什么叫持 ...
- 数据结构--树链剖分详解
数据结构--树链剖分详解 关于模板题---->传送门 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将 ...
- 线段树详解 (原理,实现与应用)
线段树详解 By 岩之痕 目录: 一:综述 二:原理 三:递归实现 四:非递归原理 五:非递归实现 六:线段树解题模型 七:扫描线 八:可持久化 (主席树) 九:练习题 一:综述 假设有编号从1到n的 ...
最新文章
- 如何进行机器学习框架选择
- 【java基础】POJO和JavaBean的区别
- python dlib学习(十):换脸
- 【博客话题】技术生涯中的出与入
- java原子操作cas_java并发编程系列二:原子操作/CAS
- ls 显示目录下的内容和文件相关属性信息
- leetcode917
- requests中获取请求到文本编码格式
- 免费网页模板提供站推荐
- Hive的Map Join与Common Join
- Dropbox的服务器和网络自动化运维实践
- java.util.function包下的四大Function
- Access数据库使用DateAdd函数更新日期信息
- ssis oracle配置,从SSIS包SQL Server连接Oracle数据库
- Vuforia入门之简单图片识别案例(一)
- 南京市公安局电子警察系统数据库扩容和异地灾备公开招标采购公告
- 使用MODBUS转PROFINET智能网关实现与多个温控器数据读写
- 02 C/C++创建tcl自定义命令
- 旋转木马图片切换展示js特效
- 蝉知CMS7.0.1后台模板Getshell
热门文章
- 【错误记录】Google Play 上架报错 ( 我们检测到您的应用程序包含未经认证的广告SDK或未经批准用于儿童导向服务的SDK )
- 【错误记录】Android Studio 创建 Flutter 应用被卡住 ( 更新 Flutter 插件 | 命令行创建 | 断网 )
- 【OpenGL】十七、OpenGL 绘制四边形 ( 绘制 GL_QUAD_STRIP 模式四边形 )
- 【C++ 语言】文件操作 ( fopen | fprintf | fscanf | fgets | fputc | fgetc | ofstream | ifstream )
- 【数理逻辑】范式 ( 合取范式 | 析取范式 | 大项 | 小项 | 极大项 | 极小项 | 主合取范式 | 主析取范式 | 等值演算方法求主析/合取范式 | 真值表法求主析/合取范式 )
- 【iOS 开发】基本 UI 控件详解 (UIButton | UITextField | UITextView | UISwitch)
- [Spring cloud 一步步实现广告系统] 21. 系统错误汇总
- HihoCoder#1509 : 异或排序(二进制)
- How to Use Git
- 数据库复习总结(12)数据检索