Hw 2 排序,序和数据结构扩展
Ⅰ Analysis
1.
(a)
将 length[A]
记为
n
n
n
- 对于内循环的正确性:
-
循环不变式:内循环每次迭代开始,子数组
A[j ... n]
中元素A[j]
是最小的,并且其元素与迭代前保持一致。 -
初始化: 第一次循环迭代前(即 j = n j = n j=n),那么循环不变式中只有一个元素,同时也为是最小元素,故循环不变式成立。
-
保持: 假设迭代之前,有循环不变式
A[j ... n]
为真。下一次进入循环体会对A[j]
与A[j−1]
的大小进行比较,并决定是否交换使j−1
位置为min{ A[j], A[j−1] }
。因为A[j]
为A[j ... n]
中的最小元素,所以可以得出A[j−1]
为A[j−1 ... n]
中最小的元素。此时子数组A[j−1 ... n]
中的元素并未发生改变。所以,此次循环迭代增加元素A[j−1]
保持了循环不变式。 -
终止: 循环终止时,子数组
A[i ... n]
由原来元素组成,且A[i]
为其中最小元素。
- 对于外循环的正确性:
-
循环不变式:外循环每次迭代开始,子数组
A[1 ... i−1]
构成升序数组,且剩余子数组A[i ... n]
中的元素均大于等于A[i−1]
。 -
初始化: 第一次循环迭代前(即 n = 1 n = 1 n=1),此时数组
A[1 ... i-1]
为空数组,故循环不变式成立。 -
保持: 假设迭代之前,有循环不变式为真。下一次进入循环体,由上面已证明的内循环正确性可知,
A[i ... n]
的元素构成并未改变,且A[i]
为最小元素。所以A[1 ... i]
符合升序排列,且 子数组A[i+1 ... n]
中的元素均大于等于A[i]
。 -
终止: 循环终止时,子数组
A[1 ... n−1]
构成升序数组,且A[n]
大于A[1 ... n−1]
中的任意元素,所以A[1 ... n]
按照升序排列,且元素与循环前保持一致。
综上所述,可证明冒泡排序终止后后,元素保持一致且有序,即证明算法的正确性。
(b)
冒泡排序 | 插入排序 | |
---|---|---|
最好情况 | Θ ( n 2 ) \Theta(n^2) Θ(n2) | Θ ( n ) \Theta(n) Θ(n) |
最坏情况 | Θ ( n 2 ) \Theta(n^2) Θ(n2) | Θ ( n 2 ) \Theta(n^2) Θ(n2) |
- 对于冒泡排序,无论何种情况,每步都会进行比较,将循环进行到底。任何情况下,总循环次数不变,比较次数未改变,所以时间开销均为 Θ ( n 2 ) \Theta(n^2) Θ(n2)。
- 对于插入排序,最坏情况下(即逆序排列),与冒泡排序一致,为 Θ ( n 2 ) \Theta(n^2) Θ(n2)。而最好情况下,插入排序只需进行 n − 1 n-1 n−1 次比较,所以时间开销为 Θ ( n ) \Theta(n) Θ(n),优于冒泡排序。
2.
首先,将数组填充至最近的 2k - 1,记作 n'
,即:若
n
=
13
n = 13
n=13, 则将数字
14
14
14,
15
15
15填充入数组。这样做的原因是,对于任意同一位的bit
,从
1
1
1 一直加到 n'
,其和必定为2k-1。
例如:当 n = 6 n = 6 n=6 时,将数字 7 7 7 填充入数组,此时 n ′ = 13 n' = 13 n′=13, k = 3 k = 3 k=3, 每一位的和为 2 k − 1 = 4 2^{k-1} = 4 2k−1=4
第3位 | 第2位 | 第1位 | |
---|---|---|---|
1 | 0 | 0 | 1 |
2 | 0 | 1 | 0 |
3 | 0 | 1 | 1 |
4 | 1 | 0 | 0 |
5 | 1 | 0 | 1 |
6 | 1 | 1 | 0 |
7 | 1 | 1 | 1 |
求和 | 4 | 4 | 4 |
- 只有当 n = 1 n = 1 n=1 时, k = 1 k = 1 k=1, 2 k − 1 2^{k-1} 2k−1 为奇数,此时数组中有 0 0 0 ,则缺少 1 1 1;有 1 1 1 ,则缺少 0 0 0 。
- 而其余情况,
2
k
−
1
2^{k-1}
2k−1 均为偶数,所以寻找
缺失数字k
的方法就是,将同一位的bit
值相加,其和若为偶数,则k
的该位为 0 0 0;和为奇数,则为 1 1 1 。
这样的话,只需要将每个数的32位,通过 fetch(A, i, j)
函数提取并计算,最终找到缺失数字,这种方法的时间开销为
Θ
(
n
)
\Theta(n)
Θ(n) 。
Ⅱ Median Selection
1.
-
先找到最小值
k
。
通过 淘汰赛 的方式寻找:两两一组比较一次,确定二者中的更小值,对于这 n 2 \frac{n}{2} 2n 个数再次两两一组比较,不断循环,最终找到 胜者,即最小值。
找到最小值总共经过了 n − 1 n-1 n−1 次比较。 -
再找次小值
考虑最小值k
:在以上n-1
次比较中,k
一共参与了 ⌈ log n ⌉ \lceil\log n\rceil ⌈logn⌉ 场 比赛,有 ⌈ log n ⌉ \lceil\log n\rceil ⌈logn⌉ 个元素 亲自输给 了k
,也就意味着次小值在这 ⌈ log n ⌉ \lceil\log n\rceil ⌈logn⌉ 个元素中。
于是,剩下的问题转变为,找到 ⌈ log n ⌉ \lceil\log n\rceil ⌈logn⌉ 个元素中的最小值,即可用上述找最小值的方法同理解决问题。
找到次小值再经过了 ⌈ log n ⌉ − 1 \lceil\log n\rceil-1 ⌈logn⌉−1 次比较。
最终,总共的比较次数只需将两者相加,为 n + ⌈ log n ⌉ − 2 n+\lceil\log n\rceil-2 n+⌈logn⌉−2 次比较。
2.
对于数组为 A[1 ... n]
,通过中位数子程序找到其中位数 A[m]
。
如果要寻找的顺序统计量k
符合:
-
k
=
m
k = m
k=m ,则返回
A[m]
。 -
k
≠
m
k \neq m
k=m ,则将数组换分为
A[1 ... m-1]
和A[m+1 ... n]
两个子数组。 k < m k < m k<m 就到A[1 ... m-1]
继续进行以上步骤; k > m k > m k>m 就到A[m+1 ... n]
中,并将k <-- m-k
。
不断递归,直至最终寻找到。
以这种方法, T ( n ) ≤ O ( n ) T(n) \le O(n) T(n)≤O(n),符合题目要求。
3.
由上题已知:找到数组的第 i 小的数是 O ( n ) O(n) O(n)。
- 先找到中位数
m
。(线性时间选择算法 --> O ( n ) O(n) O(n)) - 然后通过数组
D[1 ... n-1]
,记录处中位数以外,数组S
中每个元素到中位数距离的绝对值。(遍历数组 --> O ( n ) O(n) O(n)) - 题目中要求的:最接近中位数的
k
个元素,即为D
中最小的k
个元素。 - 再找到数组
D
中第k
小的元素D[mk]
。(线性时间选择算法 --> O ( n ) O(n) O(n)) - 最后再遍历数组
D
,找到其中k-1
个小于D[mk]
的元素。(遍历数组 --> O ( n ) O(n) O(n))
综上所述,该算法时间复杂度为O(n)。
Ⅲ Interval Tree
1.
INTERVAL(T, i)
n = T.root
result = T.nil
while n != T.nil
if i overlap n.int
if(result == T.nil || result.low < n.low)
result = n
if n.left != T.nil and n.left.max >= i.low
n = n.left
else n = n.right
return result
- 初始化变量
n
为区间树T
的根节点,n
用于遍历区间树的节点,将结果变量result
设置为T.nil
,result
用于记录找到的具有最小低端点的重叠区间。 - 先检查区间
i
是否与节点n
所表示的区间重叠。若重叠,则继续判断:如果result
是T.nil
或者result
的低端点比n
的低端点小,那么更新result
为节点n
。从而找到具有最小低端点的重叠区间。 - 之后判断:如果节点
n
的左子节点不是T.nil
,并且节点n
的左子节点的最大高端点大于等于区间i
的低端点,那么说明左子树中的某些区间可能与i
重叠。因此,将n
更新为其左子节点,继续向左子树搜索。 - 若不满足上述条件,则说明左子树中的所有区间都不与
i
重叠,那么就向右子树搜索。将n
更新为其右子节点,继续向右子树搜索。
当循环结束后,返回结果 result
:包含与区间 i 重叠且具有最小低端点的区间。
2.
Node[] result;
INTERVAL(T, i)
n = T.root
result = T.nil;
while n != T.nil
if i overlap n.int
result.add(n)
if n.left != T.nil and n.left.max >= i.int.low
m = n.successor
if(m != T.Nil && m.int.low <= i.int.high)
i' = (m.int.low, i.int.high)
INTERVAL(T, i')
n = n.left
else n = n.right
-
先检查区间
i
是否与节点n
所表示的区间重叠。若重叠,则将节点n
添加到result
中。 -
如果节点
n
的左子节点不是T.nil
,并且节点n
的左子节点的最大高端点大于等于区间i
的低端点,那么说明左子树中的某些区间可能与i
重叠。因此对节点n
的后继节点m
进行检查,并且m
的低端点不能大于区间i
的高端点,否则它们不会重叠。若成立,构造新的区间i'
,其低端点为m
的低端点,高端点为i
的高端点,然后递归调用INTERVAL(T, i')
进一步搜索。 -
再继续向右子树搜索。
-
最后得到结果。