您的位置:首页 > 理论基础 > 数据结构算法

数据结构与金融算法 18-19秋季学期 期中考试简略题解

2020-02-05 04:28 761 查看

1.一个满二叉树的定义是,所有的节点要么有两个子节点,要么没有子节点。定义Bn为:n个节点的满二叉树,对应的二叉树数量。对称的两个树分别计算。求证:Bn∈Ω(2n)

比如,B1=1,B3=1,B5=2。

 

这题的关键在于求出Bn的递推式。多画几次就能发现,实际上对于一颗Bn树,包含的情况有一边子树为B1,一边子树为Bn-2。依次类推可以得到如下递推式:

 

那么,就可以运用一下数学归纳法。如果能从Bn-2,Bn-4之类的入手,得到一个放缩的式子大于等于2n的话,那就再好不过了。

不过很可惜,如果直接Bn = 2Bn-2 x B1 + 2Bn-4 x B3 + 2Bn-6 x B5 + ... ≥ 2 · 2n-2 · 1 + 2 · 2n-4 · 1 + 2 · 2n-6 · 4 + ...,需要算到起码Bn-20之后才能满足放缩条件。这在正常做题中显然是没办法的。

但是我们可以验证(猜想),当n足够大时,B(n-1)/2是足够大于2n的。那么只要我们从折半的地方入手,很容易就能从类似B(n-1)/2 x B(n-1)/2这样的式子中推出放缩式。具体细节就不再赘述了,读者可以自行验证。

 

2. 当前有一个大小为n的无序列表,以及一个数S。设计一个O(nlgn)算法,寻找列表里面是否有两个数加和等于S。

既然是nlgn算法,当然需要先排序一下,这里的代价就是nlgn了。

然后,可以考虑这样一个线性的算法:

假设有序列表为x。现在让两个指针,left和right,分别指向x[0]和x[n-1]。

然后,若x[left] + x[right] > S,则right -= 1,而若是 < S,则left += 1。

终止条件为x[left] + x[right] == S或者left == right。

显然这是一个线性算法。代码如下:

#...处理x,已知S
left = 0
right = n-1
while left < right:
if x[left] + x[right] < S:
left += 1
else if x[left] + x[right] > S:
right -= 1
else:
return true
return false

 

举一个例子可能有助于理解:

算法的正确性可以这么考虑:由于数组有序,当left右移,l + r的大小就会增加,反之,right左移,l + r就会减小。无论l + r与S有什么关系,每一次循环总能找到进行一次退出或将right - left减少1的操作。由于right - left最大为n -1,因此这个算法是线性的。

 

3. 现在有n个处于区间[0, m]的整数,要求经过O(n+m)的预处理之后,可以用O(1)的算法返回位于指定区间[a, b]之间的数的个数。

仔细思考一下会发现,预处理其实就是制作出一个类似于离散型分布函数的数组。

设数据数组为S,分布数组为D。首先,我们可以遍历S,对于一个数i,就使D[k] += 1。然后得出一个类似分布律的东西。实际上,把数组里的每个数除以n,就得到了S的分布律。

然后,将前一个格的东西加和到后一格,就可以得出分布函数了。

最后要注意,查询函数是D[b] - D[a - 1],否则就会缺少a上的数值了!

 

4. 对于一个栈,给定两个操作:

Ordered-push( x ):如果x < 栈顶元素first,就弹出first,直到满足x ≥ first为止,然后插入x。

Pop( x ):弹出first。

用摊还分析计算操作代价。

考虑实际代价:

  • O-push:2i + 2。假设弹出了i个元素,则比较次数为i + 1,弹出次数为i,插入次数为1,共2i + 2。
  • Pop:1。

那么,如何分析会计代价呢?

一般的会计代价分析,需要将一个大代价的操作平摊到小代价操作上。然而由于这题的O-push大部分的代价本质上也是pop,所以分摊到小pop操作上显然是不合适的。

所以,这题需要O-push自我分摊。方案是这样的:

  • 弹出i个元素之前,必然需要先进行i次O-push操作。
  • 那么,就可以将2i分摊到pop出的i个元素中,每个元素分配2的代价。

这样,会计代价就是:

  • O-push:-2i + 2。-2i是分摊出去的,2是被分摊到的。
  • Pop:0

所以,摊还代价为:

  • O-push:4
  • Pop:1

整个数据类型的操作代价是O(1)。

 

5. 对于一个有向图,

(1)设计O(n+m)的算法,判断从一个特定的点v是否可以到达图上所有点;

(2)设计O(n+m)的算法,判断图中是否存在点,可达图上所有点。

首先摆dfs框架:

def dfs(adjVers, color, v):
color[v] = gray
<preorder proc>
remAdj = adjVers[v]
while remAdj:
w = remAdj.first()
if (color[v] == white):
<exploratory proc for vw>
wAns = dfs(adjVers, color, w)
<backtrace proc for vw>
else:
<checking nontree edge vw>
remAdj = remAdj.rest()
<postorder proc>
color[v] = black
return ans

先看看第一题。方法是多种多样的。

  • 我们可以利用活跃区间,即记录一个点探索到的时间和结束探索的时间。在从v开始进行dfs的时候,如果v是最后一个结束访问的节点,即可以得出v可以到达所有的点。
  • 除此之外,也可以利用点的着色,看看v之后是否还有白色点。如果没有则v满足条件。
  • 我自己的做法有点绕:在dfs过程中,记录一个点以自己为根的子树包含的节点数。如果v的这个数为n,说明v满足条件。

对于我自己的做法,要对dfs做出如下更改:

#preorder proc
ans = 1

#backtrace proc for vw
ans += wAns

而对于第二题,关键在于,在dfs结束后,最后一个dfs树的根是否可达所有的点。至于为什么可以仔细想想,大概可以往反证法的方向考虑。

所以基于这个思想,可以在dfs过程中记录这个最后的根,并在dfs结束后对这个根运用(1)的算法。

仔细考虑会发现,如果这样的点存在,那么如果在dfs过程中,在寻找到根时把根接入,那么最终只会剩下一个dfs树。

这个时候既可以用记录自根子树节点数的方法,也可以用单纯记录根的方法。殊途同归。

然而要注意,用自根子树节点数方法时,其实也需要记录根,而不能仅仅靠黑色节点。否则会出现问题。

 

6. 一个二进制数N有n位,可以进行以下操作:

1. Addition(x, y),将x+y的结果保存在x中。

2. Shift(x, d),将x左移或右移一位,方向取决于d,1为左移,-1为右移。

现在用以上操作设计两个算法:

1. 用O(n)次Addition和Shift求出N2。

2. 用O(n2)次Addition和Shift求出⌈√N⌉。

第一题还是很好做的。如果自己模拟一下乘法,就能知道怎么写这个算法。

def Pow(x):
f = x
ans = 0
for i = 1 to n:
if x[i] == 1:
Addition(ans, f)
Shift(f, 1)
return ans

 

在第一题的基础上,第二题其实也不难做了。考虑一下,如果是普通的十进制数,可以用二分搜索的方式——找到一个Pow(k) == N的数就行了。同样的,在这里也可以用二分搜索。但是要注意的是,不能混淆了N和n——n是位数,N的最大大小其实是2n。

 

转载于:https://www.cnblogs.com/KakagouLT/p/9998895.html

  • 点赞
  • 收藏
  • 分享
  • 文章举报
dongchensou2828 发布了0 篇原创文章 · 获赞 0 · 访问量 171 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: