树结构在程序设计中的运用

                                                                                引言
近年来,由于各种竞赛纷纷采用free-pascal,因此对于算法来说,空间效率上的要求降低了,而对时间效率却提出了更高的要求。这使得选手不仅要娴熟地掌握常规算法,而且要大胆创新,构造更高效的算法来解决问题。
 
在以往的程序设计中,链式结构采用得较多。的确链式结构有编程复杂度低、简单易懂等优点,但有一个致命的弱点:相邻的两个元素间的联系并不明显。而树结构却能很好的做到这一点。
竞赛中会经常遇到这样的题目:给出各个元素之间的联系,要求将这些元素 分成几个集合,每个集合中的元素直接或间接有联系。在这类问题中主要涉 及的是对集合的合并和查找,因此将这种集合称为并查集。
                                                                              并查集
一.链结构的并查集
 
  二.树结构的并查集
 
        1.链结构的并查集
链表被普通用来计算并查集:表中的每个元素设两个指针:一个指向同一集合中的下一个元素;另一个指向表首元素。采用链式存储结构,在进行集合的查找时的算 法复杂度仅为 O 1 );但合并集合时的算法复杂度却达到了 O n )。如果我们希望两种基本操作的时间效率都比较高的话,链式存储方式就 力不从心 了。
 
        2.树结构的并查集
采用树结构支持并查集的计算能够满足我们的要求。并查集与一般的树结构不同,每个顶点纪录的不是它的子结点,而是将它的父结点记录下来。下面我介绍一下树结构的并查集的两种运算方式
 
n
n⑴直接在树中查询
n⑵边查询边“路径压缩”
 
对应与前面的链式存储结构,树状结构的优势非常明显:编程复杂度低;时间效率高。
 
               (1).直接在树中查
集合的合并算法很简单,只要将两棵树的根结点相连即可,这步操作只要O(1)时间复杂度。所以算法的时间效率取决于集合查找的快慢。而集合的查找效率与树的 深度呈线性关系。因此直接查询所需要的时间复杂度平均为O(logN)。但在最坏情况下,树退化成为一条链,使得每一次查询的算法复杂度为O(N)。
 
               (2).边 询边 路径压缩
其实,我们还能将集合查找的算法复杂度进一步降低:采用 路径压缩 算法。它的想法很简单:在集合的查找过程中顺便将树的深度降低。采用路径压缩后,每一次查询所用的时间复杂度为增长极为缓慢的 ackerman 函数的反函数 —— α(x)。对于可以想象到的n,α(n)都是在5之内的。
 
 
 
 
                                                                          线段树
处理涉及到图形的面积、周长等问题的时候,并不需要依赖很深的数学知识,但要提高处理此类问题的效率却又十分困难。这就需要从根本上改变算法的基础 —— 数据结构。这里要说的就是一种特殊的数据结构 —— 线段树
先看一道较基础的题目: 给出区间上的n条线段,判断这些线段覆盖到的区间大小。 通过这题我们来逐步认识线段树。
 
     一.线段树的定义
     二.在线段树中加入和删除线段
     三.计算测度和连续线段数
 
 
              1.线段树的定义
线段树是一棵二叉树,将一个区间划分为一个个[i,i+1]的单元区间,每个单元区间对应线段树中的一个叶子结点。每个结点用一个变量count来记录覆盖该结点的线段条数。
 
区间[1,7]所对应的线段树如下图所示。区间上有一条线段[3,6]。
(  电脑弱,搞不上去,望理解)
                2.在线段树中插入和删除线段
在线段树中插入和删除线段的方法类似,都采用递归逐层向两个子结点扫 描,直到线段能够盖满结点表示的整个区间为止。
 
经过分析可以发现:在线段树中插入、删除线段的时间复杂度均为O(logN)。
计算测度和连续线段数
结点的测度m指的是结点所表示区间中线段覆盖过的长度。
        j - i                       (count>0)
m=  0                              (count=0 且结点为叶结点)
         lch.m + rch.m    (count=0 且结点为内部结点)
 
                3.计算测度和连续线段数
连续线段数line指的是区间中互不相交的线段条数。
连续线段数并不能像测度那样将两个子结点中的连续线段数简单相加。于是我们引进了两个量lbd,rbd,分别表示区间的左右两端是否被线段覆盖。
           1 (左端点被线段覆盖到)
lbd  =  0 (左端点不被线段覆盖到)
 
           1   (右端点被线段覆盖到)
rbd  =  0 (右端点不被线段覆盖到)
 
line可以根据lbd,rbd定义如下:
        1       (count > 0)
        0    (count=0 且结点为叶结点)
 Line=  lch^.Line + rch^.Line - 1    (count=0 且结点为内部结点, lch^.rbd、rch^.lbd都为1)
           lch^.Line + rch^.Line        (count=0且结点为内部结点,      lch^.rbd,rch^.Lbd不同时为1)
 
                                                           
                                     
 
                                                                          树状数组
IOI2001 中的 MOBILE 难倒了很多选手。虽然该题的题意十分简单:在一个矩阵中,通过更新元素值修改矩阵状态,并计算某子矩阵的数字和,但难点在于数据的规模极大。下面,我来介绍一种新的数据结构 树状数组
 
1 、建立树状数组 C
2 、更新元素值
3 、子序列求和
 
利用树状数组,编程的复杂度提高了,但程序的时间效率也大幅地提高。这正是利用了树结构能够减少搜索范围,将信息集中起来的优点,让更新数组和求和运算牵连尽量少的变量。
        1.建立树状数组C
先将问题简化,考察一维子序列求和的算法。设该序列为 a[1] a[2] …… a[n]
w
算法1:直接在原序列中计算。显然更新元素值的时间复杂度为O(1);在最坏情况下,子序列求和的时间复杂度为O(n)。
算法 2 :增加数组 b ,其中 b[i] a[1]+a[2]+ …… +a[i]。由于a[i]的更改影响b[i]┅b[n],因此在最坏情况下更新元素值的算法复杂度为O(n);而子序列求和的算法 复杂度仅为O(1)。
以上两种算法,要么在更新元素值上耗费时间过长(算法1),要么在子序列求和上无法避免大量运算(算法2)。有没有更好的方法呢?
算法三:增加数组C,其中C[i]=a[i-2 k +1]+ …… +a[i](k为i在二进制形式下末尾0的个数)。由c数组的定义可以得出
c[1]=a[1]
c[2]=a[1]+a[2]=c[1]+a[2]
c[3]=a[3]
c[4]=a[1]+a[2]+a[3]+a[4]=c[2]+c[3]+a[4]
c[5]=a[5]
c[6]=a[5]+a[6]=c[5]+a[6]
………………
数组的结构对应一棵树,因此称之为树状数组。
 
在统计更新元素值和子序列求和的算法复杂度后,会发现两种操作的时间复杂度均为O(logN),大大提高了算法效率。
 
        2.更新元素值
定理
若a[k]所牵动的序列为C[p 1 ],C[p 2 ] …… C[p m ]。
则p 1 =k,而p i+1 =p i +2 li (l i 为p i 在二进制中末尾0的个数)。
由此得出更改元素值的方法:若将x添加到a[k],则c数组中c[p 1 ]、 c[p 2 ] 、c[p m ](p m ≤n<p m+1 )受其影响,亦应该添加x。
 
例如a[1]……a[9]中,a[3] 添加x;
p1=k=3          p2=3+20=4
p3=4+22=8       p4=8+23=16>9
    由此得出,c[3]、c[4]、c[8]亦应该添加x。
 
            3.子序列求和
子序列求和可以转化为求由 a[1] 开始的序列 a[1] …… a[k]的和S。
而在树状数组中求S十分简单:
       根据c[k]=a[k-2 l +1]+ +a[k] (l为k在二进制数中末尾0的个数)
我们从k 1 =k出发,按照
k i+1 =k i - 2 lki (lki为k i 在二进制数中末尾0的个数)   公式一
递推k 2 ,k 3 ,k m  (k m+1 =0)。由此得出
S=c[k 1 ]+c[k 2 ]+c[k 3 ] + + c[k m ]
例如,计算a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]
k1=7
k2= k1-2l1=7-20=6
k3= k2-2l2=6-21=4
k4= k3-2l3=4-22=0
    即a[1]+a[2]+ a[3]+a[4]+ a[5]+a[6]+ a[7]=c[7]+c[6]+c[4]
推广到二维,同样也只需要计算由(1,1)开始的矩阵中的数字和,然后通过加减来计算子矩阵中的数字和。
 
 
 
 
                                                                             小结
在上面的例子中我们可以看出采用树结构处理问题时,往往能够缩小搜索范围。这样在每次的查找过程中都能正确地选择由根开始的一条路径,不会出现无用的搜索
由于通常都是选取一条由根开始的路径,所以遍历树的快慢很大程度上取决于树的深度。如何降低树的高度就成了算法的关键。
相对于传统意义上的树,树状数组就其形式并不能称为“树”,但其核心思想却与树结构如出一辙。
 
                                                                                                                                                                                <Marvolo原创,严禁转载>

转载于:https://www.cnblogs.com/zhtjtcz/p/4992986.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值