【算法】最近公共祖先之在线算法(RMQ-ST)
来源:互联网 发布:js 判断数组是否为空 编辑:程序博客网 时间:2024/06/05 19:55
在线算法是基于RMQ-ST算法的基础上进行的
RMQ问题求解算法
给定一个整型数组,长度为n,寻找区间内的极值,m表示询问的次数。求解算法不外乎下面两种:
1、最基础的算法就是每次都遍历一次区间则时间复杂度为o(k*m)(k<=n)
2、对数组预处理,即先求出所有区间组合的极值,这样在给定询问区间时求极值时间复杂度降低,但是预处理又分为两种情况:
- 2.1 两层循环,时间复杂度o(
n2 ),总的时间复杂度为o(n2 +m) - 2.2 分段处理,分成大小为
sqrt(n) 的段,记录段内极值,这样预处理的时间复杂度是o(n),整的时间复杂度是o(n+msqrt(n) )
但是有一种巧妙的算法即ST算法,时间复杂度是o(nlogn+m),试想如果询问次数m>>n时,这个算法的效率就明显提升了。
稀疏表算法(ST)
预处理
这个算法其实是转化为长度为
- M[i][j]=min{M[i][j-1],M[i+
2j−1 -1][j-1]}
这个式子意思很明显,如果求区间A[i,….,i+2j -1]这个长度以i为起始,2j 为长度区间的最小值,可以将这个区间划分为相等的两部分,即A[i,….,i+2j−1 -1]和A[i+2j−1 ,….,i+2j -1],则原区间A[i,….,i+2j -1]最小值必然在A[i,….,i+2j−1 -1]和A[i+2j−1 ,….,i+2j -1]中的最小一方。
以此往前递推,当j=0时,M[i][0]=A[i],长度为1时,最小值就是元素自己,而j取的最大值是log2n
求M[i][j]算法代码如下:
private static void RMQ_ST(int[][] M, int[] A,int len) { //j=0时,M最小值就是元素本身 for(int i=0;i<len;i++){ M[i][0]=A[i]; } int elen = (int)(Math.log(len)/Math.log(2)); //求从1,2,4...到小于n但是却是2的幂数的区间段的最小值 for(int j=1;j<=elen;j++){ for(int i=0;i+(1<<j)-1<len;i++){ M[i][j][0]=Math.min(M[i][j-1], min[i+(1<<(j-1))][j-1]); } } }
计算给定任意区间的最小值
假设求A[p,q]区间的最小值,则转化为长度以2为指数的区间,利用M数组计算,而且转换的两个区间段至少是覆盖了p,q区间,允许而且也是最有可能两段区间重叠。如何转化长度以2为指数的区间呢?公式如下:
- j=
log2(p−q+1) - minA[p,q]=min{ M[p][j],M[q-
2j -1][j] }
所以只要一步就可以直接求出询问区间的最小值,时间复杂度o(1)
利用RMQ-ST求解最近公共祖先
假设一棵树
首先进行深度遍历,而且进入和出来访问到节点都要记录,则得到序列
- 数组E(存储遍历结果):
- 数组L(存储节点深度):
- Map nodesfirstindex(记录深度遍历时第一次出现的位置)
在上面这些遍历得到的情况下,如果我们求某两个节点p=9、q=12的最近祖先,则首先根据节点p、q在Map nodesfirstindex找到深度遍历第一次出现的位置对应为9和14,然后在数组L则求区间L[9,..,14]的最小深度值,毫无疑问这个就是用RMQ-ST算法求区间极值啦。求出最小深度值为3对应的顺序下标为11,然后在存储遍历结果的数组E中找到元素E[11]=3,即p=9、q=12的最近祖先节点为3.
最终求解程序代码如下,该代码稍微去掉一些打印信息就可以被Hiho#1069 : 最近公共祖先·三AC:
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Map.Entry;import java.util.Scanner;public class LCA_online { static int indexs=0;//遍历的节点顺序下标 public static void main(String[] args) { Scanner sc = new Scanner(System.in); Map<String,List<String>> nodes = new HashMap<>();//记录这棵树 Map<String,Integer> nodesfirstindex = new HashMap<>();//记录深度遍历时第一次出现的位置 int N = sc.nextInt(); int len=2*N+1; String[] E = new String[len];//存储遍历结果 int[] L = new int[len];//存储当前节点深度 int elen = (int) (Math.log(len)/Math.log(2)); int[][][] min = new int[len][elen+1][2]; String head=null; boolean iffirst=true; while(N-->0){ String node1 = sc.next(); String node2 = sc.next(); if(iffirst){ head=node1; iffirst=false; } List<String> childs = nodes.get(node1); if(childs==null){ childs = new ArrayList<>(); } childs.add(node2); nodes.put(node1, childs); } int deep=0; //深度遍历这棵树 dfs(head,nodes,nodesfirstindex,E,L,deep); //打印相关信息 System.out.println(java.util.Arrays.toString(E)); System.out.println(java.util.Arrays.toString(L)); for(Entry<String,Integer>entry:nodesfirstindex.entrySet()){ System.out.print(entry.getKey()+":"+entry.getValue()+"\t"); } System.out.println(); //RMQ_ST RMQ_ST(min,L,len,elen); //打印min for(int ej=0;ej<=elen;ej++){ for(int i=0;i<len;i++){ System.out.print(min[i][ej][0]+","+min[i][ej][1]+"\t"); } System.out.println(); } int M = sc.nextInt(); while(M-->0){ String node1 = sc.next(); String node2 = sc.next(); int node1index = nodesfirstindex.get(node1); int node2index = nodesfirstindex.get(node2); int temp = node1index; if(node1index>node2index){ node1index=node2index; node2index=temp; } System.out.println(E[getMin(node1index,node2index,min)]); } } //获取指定区间的最短深度 private static int getMin(int node1index, int node2index,int [][][] min) { int ej = (int) (Math.log(node2index-node1index+1)/Math.log(2)); int s1 = min[node1index][ej][0]; int s2 = min[node2index-(1<<ej)+1][ej][0]; if(s1<s2){ return min[node1index][ej][1]; }else{ return min[node2index-(1<<ej)+1][ej][1]; } } //利用RMQ-ST算法求区间内的节点最短深度 private static void RMQ_ST(int[][][] min, int[] L,int len,int elen) { for(int i=0;i<len;i++){ min[i][0][0]=L[i]; min[i][0][1]=i; } for(int ej=1;ej<=elen;ej++){ for(int i=0;i+(1<<ej)-1<len;i++){ min[i][ej][0]=Math.min(min[i][ej-1][0], min[i+(1<<(ej-1))][ej-1][0]); if(min[i][ej][0]==min[i][ej-1][0]) min[i][ej][1]=min[i][ej-1][1]; else min[i][ej][1]=min[i+(1<<(ej-1))][ej-1][1]; } } } //深度遍历 private static void dfs(String head,Map<String, List<String>> nodes, Map<String, Integer> nodesfirstindex, String[] E, int[] L, int deep) { if(head==null) return; if(nodesfirstindex.get(head)==null){//记录第一次遍历到的位置 nodesfirstindex.put(head, indexs); } E[indexs]=head;//记录当前遍历节点 L[indexs]=deep;//记录当前节点深度 indexs++; List<String> childs = nodes.get(head); if(childs==null) return; for(int i=0;i<childs.size();i++){ dfs(childs.get(i),nodes,nodesfirstindex,E,L,deep+1); E[indexs]=head;//记录当前遍历节点 L[indexs]=deep;//记录当前节点深度 indexs++; } }}
运行结果:
结论
本算法参考了编程之法上的求解算法,和其他博客帖子,在此不一一鸣谢,如有违权,深表歉意,小弟不才,欢迎大牛拍砖。
- 【算法】最近公共祖先之在线算法(RMQ-ST)
- LCA(最近公共祖先算法)之在线st表法
- 求LCA最近公共祖先的在线ST算法_C++
- 最近公共祖先(LCA)问题-在线ST算法
- LCA最近公共祖先的离线算法(Tarjan)和在线算法(ST)
- hihoCoder 1069 最近公共祖先 在线算法
- POJ1330.Nearest Common Ancestors——最近公共祖先(dfs+ST在线算法)
- HIHO #1069 : 最近公共祖先·三(RMQ+DFS LCA在线算法)
- 最近公共祖先(LCA)算法实现过程 【Tarjan离线+倍增在线+RMQ】
- 算法基础 - 最近公共祖先(在线算法/离线算法)
- LCA最近公共祖先 在线算法和离线算法 模板
- 最近公共祖先--RMQ
- 最近公共祖先(LCA):离线&在线算法
- HDU 2586 最近公共祖先 LCA在线算法
- 最近公共祖先 朴素 离线 在线 算法合集
- [笔记]LCA最近公共祖先---倍增在线算法
- RMQ之ST算法
- 最近公共祖先,Targin算法
- EOJ-1270 Arbitrage(套利交易)
- 在vmare的虚拟机上部署spark1.5.2的ha(成功)和在openstack的虚拟机上部署spark1.5.2的ha(失败)
- Linux Shell系列教程之(三)Shell变量
- JAVA输入输出
- NYOJ 1253 Turing equation
- 【算法】最近公共祖先之在线算法(RMQ-ST)
- 微信公众号支付jsapi
- leetcode 318. Maximum Product of Word Lengths
- ASP.NET Image控件即时刷新
- java基础总结
- iOS开发----懒加载
- 微信批量获取用户基本信息接口
- 一台机器上启动多个Tomcat
- Hibernate多对多级联 注解