随笔分类 -  数据结构和算法

摘要:配对堆(Pairing Heap)是一个简单实用的min-heap结构(当然也可以做成max-heap)。它是一颗多路树(multiway tree),类似于Leftist Heap和Skew Heap,但是与Binomial Tree和Fibonacci Heap不一样。它的基本操作是两个多路树的连接(link),所以取名叫Pairing Heap。连接操作(参考以下实现中的方法linkPair)类似于Binomial Tree和Fibonacci Heap中的link操作,即将root key值最大的树作为key值最小的树的孩子(一般作为最左边的孩子,特别是Binomial Heap必须这 阅读全文
posted @ 2011-09-06 11:09 ljsspace 阅读(4056) 评论(3) 推荐(0)
摘要:Fibonacci Heap(简称F-Heap)是一种基于二项堆的非常灵活的数据结构。它与二项堆不同的地方在于:1)root list和任何结点的child list使用双向循环链表,而且这些lists中的结点不再有先后次序(Binomial Heap中root list的根结点按degree从小到大顺序,child list的结点按degree从大到小顺序);2)二项堆中任何一颗Binomial Tree中根结点的degree是最大的,而F-Heap中由于decrease-key操作(cut和cascading cut)的缘故,并不能保证根结点的degree最大;3)二项堆中任何结点(deg 阅读全文
posted @ 2011-09-05 14:11 ljsspace 阅读(1495) 评论(0) 推荐(0)
摘要:Just like leftist or skew heaps, a binomial heap is also a mergeable heap data structure. But unlike those 'lopsided' heaps that are composed of only single binary tree, a binomial heap consists of a set of trees that are called binomial trees. Binomial trees are so called because the number 阅读全文
posted @ 2011-08-29 15:36 ljsspace 阅读(600) 评论(0) 推荐(0)
摘要:斜堆(Skew Heap)基于左倾堆的概念,也是一个用于快速合并的堆结构,但它可自我调整(self-adjusting),每一个merge操作的平摊成本仍为O(logN),其中N为结点数,而且和左倾堆相比,每个结点没有npl属性,从而节省了空间。斜堆并不能保证左倾,但是每一个合并操作(也是采取右路合并)同时需要无条件交换(而左倾堆中只是根据左右子树的npl值不符合要求时才交换)左右子树,让新合并的右子树变成左子树,原来的左子树变成新的右子树(这有点类似于Splay Tree中的做法),从而可以达到平摊意义上的左倾效果。注意:一颗子树r和NULL子树合并时并不需要交换r子树的左右子树。由于斜堆并 阅读全文
posted @ 2011-08-25 00:14 ljsspace 阅读(1658) 评论(0) 推荐(0)
摘要:Tarjan算法解决LCA查询要求事先知道全部查询提问,如果LCA要求即时询问即时回答,就需要用到下面介绍的在线算法。在线算法需要对任意树进行预处理,设输入树的结点个数为n,该算法的预处理时间和空间复杂度都是O(n),查询复杂度为O(1)。本算法的基本思想是将对一棵树T做预处理,生成每个结点的额外信息,然后利用结点的额外信息将T映射到一颗完全二叉树B,最后利用完全二叉树的特性,通过二进制位运算可以在常数时间内查询出任何两个结点x和y的LCA。*****************预处理*****************预处理T需要生成每个结点的inlabel值,ascendant值,parent和 阅读全文
posted @ 2011-08-24 00:40 ljsspace 阅读(4435) 评论(3) 推荐(0)
摘要:左倾堆(Leftist Heap)是一个便于merge操作的数据结构,通过左倾树(Leftist Tree)实现。左倾树是一种特殊的二叉树,树中结点除了满足普通二叉堆的key大小规定外,还要求每一个结点X的左子树的Null Path Length(NPL)值不小于右子树的NPL值,因此这也是与普通二叉堆的区别:虽然普通二叉堆也满足左倾树的条件,左倾树往往不是一棵完全二叉树(而且通常不平衡),从而左倾树不能用数组表示了。上面提到的NPL指的是某个结点到NULL结点(总共有n+1个NULL结点)的最短路径长度,规定NULL结点本身的NPL等于-1,叶子结点的NPL等于0,非叶结点的NPL等于它的两 阅读全文
posted @ 2011-08-21 00:21 ljsspace 阅读(727) 评论(3) 推荐(0)
摘要:LCA的Tarjan离线(offline)算法中,通过后序DFS遍历多叉树(结点数为n),利用并查集算法(disjoint sets‘ union-find operations),可以在线性时间O(n+|P|)内找到事先给定(即offline的含义)的|P|个成对结点的LCA。具体做法如下:1)首先所有的结点通过makeSet(x)调用放到各自独立集合中,然后在用lca(u)递归调用任何一个结点u时,按照从左到右顺序依次遍历u的所有子树v1,v2...vk,在v1被访问之后,v1的属性visited标记为true(此时u的属性visited还是false),然后将v1子树中所有结点与u结点合 阅读全文
posted @ 2011-08-16 12:05 ljsspace 阅读(2495) 评论(0) 推荐(1)
摘要:树状数组中每个元素覆盖了不同长度的子区间,类似于稀疏表(ST)算法的思想,每一个数组元素存储了输入数列A在该区间的最小值下标。注意:这里树状数组不是用来存储区间累加值,而是区间的最小值下标。这里针对预处理阶段提供两个算法:方法1(参考以下实现代码中的方法preprocess)采用类似于累加和中的update做法,每一个元素A[i]需要处理树状数组T中O(logn)个受影响的元素,因此预处理复杂度为O(nlogn)。方法2(参考以下实现代码中的方法preprocess2或preprocess3)利用DP(动态规划)思想,观察到树状数组中每一个下标为i的元素覆盖的区间都可以划分为:r个子区间 + 阅读全文
posted @ 2011-08-10 11:56 ljsspace 阅读(964) 评论(0) 推荐(0)
摘要:树状数组(BIT - Binary Indexed Tree)是一个用数组表示的树型数据结构,最早用于频次累计表中。树状数组中每个元素维护一个频次表A的特定部分区段的累加和。假设输入动态序列为A,A中元素值可以被修改,如果使用直接蛮力法来查询一个区间i..j的累加和,复杂度为O(n),而树状数组由于每个元素存储的是部分累加和,通过数组下标的二进制索引规律可以在O(logn)时间内找到1...j或i...j的累加和。存储空间只需要维护树状数组T,A的元素值可以很容易地从T得到(即特例i=j时的区间查询)。数组A下标通常从0开始,而树状数组的有效下标是从1开始。下面关于lowbit的讨论假设数组A 阅读全文
posted @ 2011-08-09 00:44 ljsspace 阅读(452) 评论(0) 推荐(0)
摘要:在普通RMQ问题的<O(n),O(1)>算法中,由于需要构造Cartesian Tree和得到Euler tour,两个2*n-1大小的数组E和L使得空间消耗增加O(4*n)。本文介绍的Fischer-Heun算法绕过构建Cartesian Tree的步骤,也不需要将普通RMQ转化为RMQ+1/-1问题再求解。该算法的基本思想也是采用RMQ+1/-1的分组处理, 分别生成组内的Lookup Table和组外的稀疏表。但是在每个组的大小缩小了一倍,成为s=(logn)/4。由于每个组对应一颗笛卡尔树,若两个不同的组对应的两棵笛卡尔树相同的话,那么这两个组的lookup table完全 阅读全文
posted @ 2011-08-07 14:54 ljsspace 阅读(620) 评论(0) 推荐(0)
摘要:基本思想是通过对问题的转化,最终得到<O(n),O(1)>时间复杂度。该算法分以下两大步骤:1)将RMQ问题转化为LCA问题:先构建输入数列A的笛卡尔树,构建笛卡尔树的复杂度为O(n)。2)将LCA问题转化为RMQ+1/-1问题:通过对笛卡尔树的DFS遍历得到欧拉路径(Euler Tour),建立三个数组E,L和R。其中E和L大小是2*n-1,E中元素表示笛卡尔树中每个结点的label值(实际上就是数列A的下标索引值),L中元素对应欧拉路径上每一个被访问结点的深度(即从root到该结点的深度,root本身的深度为0); R是笛卡尔树中的每一个结点第一次被访问时在E中的位置,即E[R 阅读全文
posted @ 2011-08-06 01:02 ljsspace 阅读(445) 评论(0) 推荐(0)
摘要:RMQ+1/-1问题要求数列中相邻两个元素相差+1或-1。利用这个限定条件可以使该算法复杂度总体上达到<O(n),O(1)>。具体做法是:1) 设数列A的大小为n,先对数列A分组,每组大小为b=1/2.logn (之所以这样分是为了将预处理复杂度从O(nlogn)降为O(n)),共分为n/b个组;以下第2到第4步完成RMQ+1/-1问题的预处理阶段(参考以下实现中的preprocess方法)。2)生成O(sqrt(n))个LU表P[][]和一个block类型数组T[]: 对每个组内部进行预处理(inblock preprocessing)(参考以下实现中的makeLUTable方法 阅读全文
posted @ 2011-08-04 14:28 ljsspace 阅读(416) 评论(0) 推荐(0)
摘要:ST(Sparse Table)算法的基本思想是,预先计算从起点A[i]开始长度为2的j次方(j=0,1...logn)的区间的最小值,然后在查询时将任何一个区间A[i..j]划分为两个预处理好的可能重叠的区间,取这两个重叠区间的最小值。在预处理阶段,从起点A[i]开始,任何一个长度为2^j的区间都可以划分为两个长度2^(j-1)的区间,其中第一个区间的范围为:i...i+2^(j-1)-1;第二个区间的范围为:i+2^(j-1)...i+2^j-1。用M[i,j]表示从A[i]开始,长度为2^j的区间(即A[i]...A[i+2^j-1])最小值对应的下标,那么A[M[i,j]] = min 阅读全文
posted @ 2011-08-04 14:26 ljsspace 阅读(1117) 评论(0) 推荐(0)
摘要:RMQ(Range Minimum Query)问题是计算一个输入数列A[0...n-1]从位置i到位置j之间的最小值,即RMQ[i,j]=min{A[k], k=i,i+1...j}。RMQ的解法有很多,比如Sparse Table(ST)算法(注意这个ST缩写不是指Segement Tree哦)和转化为特殊的+1/-1 RMQ的算法。为了查询的方便,RMQ算法需要对数列A进行预处理(preprocessing),如果用<f(n),g(n)>分别表示RMQ算法的预处理复杂度和查询复杂度的话,用线段树(segment tree)解决RMQ问题的复杂度为<O(n),O(logn 阅读全文
posted @ 2011-08-04 14:25 ljsspace 阅读(451) 评论(0) 推荐(0)
摘要:设模式串的长度为m,文本的长度为n,使用后缀数组做文本匹配,如果只用后缀表suftab和折半查找算法的复杂度为O(m*logn); 如果使用最长公共前缀表lcptab和折半查找算法,复杂度可以降至O(m+logn);使用增强型后缀数组(ESA)表childtab,复杂度为O(m)。本文使用复杂度为O(m)的算法,在匹配之前要求先构造SA(下面采用DC3算法构造后缀数组),然后计算出后缀数组的suftab,lcptab和childtab。由于通过childtab可以在O(1)时间复杂度内找到每一个lcp-interval的所有child-interval,因此这跟后缀树的自顶向下匹配模式串的算法 阅读全文
posted @ 2011-07-28 00:53 ljsspace 阅读(653) 评论(0) 推荐(0)
摘要:相比后缀树,后缀数组的优势是存储空间小,相关算法效率高。但是若存放childtab还是使用up,down和nextLIndex三个属性,这显然不符合后缀数组节省空间的"第一原则"。幸运的是,可以压缩存储childtab,将三个属性up,down和nextLIndex变成一个一维数组。后缀数组childtab压缩存储的基本思路是保留所有的nextLIndex值(因为nextLIndex没有冗余),将大部分冗余的down值剔除(剩下部分的down值存放到空白的nextLIndex位置中),然后将up值放到空白的nextLIndex位置。首先给出lcp-interval和chil 阅读全文
posted @ 2011-07-26 15:31 ljsspace 阅读(657) 评论(0) 推荐(0)
摘要:后缀数组自底向上遍历等价于后缀树的自底向上遍历。由于后缀数组不是树型结构,在遍历时除了SA本身之外还需要额外的信息,这时Suffix Array就是一个增强的后缀数组(Enhanced Suffix Array)了。该算法使用后缀数组的一个增强信息---LCP表,并通过堆栈模拟自底向上的遍历。遍历的结果就是一颗虚拟的lcp-interval树,其中每一个结点对应后缀树的一个内部结点。有些应用中,遍历时需要知道每个结点的孩子信息,因此在下面的实现中提供了两个版本bottomUpTraverseWithoutChildren和bottomUpTraverseWithChildren。需要说明的是, 阅读全文
posted @ 2011-07-26 15:28 ljsspace 阅读(641) 评论(0) 推荐(0)
摘要:静态链表插入排序(List Insertion Sort)算法是直接插入排序的一个变种。它的改进目的是减少在直接插入排序中的移动次数(当然这个改进并没有降低复杂度,仍为O(n^2)),因此在原输入数据的基础上附加了一个静态链表(为了减少空间的消耗,静态链表实际上就是用等长的下标数组link来模拟的,而不需要建立一个等长的动态链结构)。该算法的缺点是:虽然减少了移动次数,但是需要增加一个等长度的link数组,所以也是用空间换取时间的做法。设输入数组为v[], 在按升序排好序的状态下,设静态链表link[]中某个位置i的元素值为x=v[i],那么link[i]存储的是该元素x的下一个比它大的元素y 阅读全文
posted @ 2011-07-21 00:02 ljsspace 阅读(804) 评论(0) 推荐(0)
摘要:所谓LCP(Longest Common Prefix)是指后缀数组中相邻两个后缀的最长公共前缀的长度。在后缀数组的应用中,LCP是很重要的信息。设后缀数组为SA, 用LCP(i)定义为第SA[i]个后缀和第SA[i-1]个后缀之间的最长公共前缀长度。由于输入文本T的第p个后缀和第p-1个后缀之间存在如下关系:LCP(p) >= LCP(p-1) - 1,因此如果已知第p-1个后缀的LCP(p-1),那么在计算第p个后缀的LCP(p)时,可以直接跳过第p个后缀的前LCP(p-1)-1个字符,然后在下一个字符位置开始与后缀数组中与p相邻的前一个后缀(设它为文本T的第q个后缀,即q=SA[R 阅读全文
posted @ 2011-07-20 00:24 ljsspace 阅读(1044) 评论(0) 推荐(0)
摘要:DC3算法(Difference Cover mod 3)是J. Kärkkäinen和P. Sanders在2003年发表的论文 "Simple Linear Work Suffix Array Construction"中描述的线性时间内构造后缀数组的算法。相对Prefix Doubling(前缀倍增)算法而言,虽然它的渐进时间复杂度比较小,但是常数项比较大。DC3算法的思想类似于找中位数的median of medians算法(http://en.wikipedia.org/wiki/Selection_algorithm),它采用分治思想: 先用 阅读全文
posted @ 2011-07-19 15:16 ljsspace 阅读(1402) 评论(0) 推荐(0)