【经典阅读】《算法导论》Chapter1-2 Getting Started
1. 预习部分
-
伪代码约定
- 缩进结构
- 循环计数器迭代增加用 t o to to,减小用 d o w n t o downto downto,变化量大于1时前面加个 b y by by
- 全为局部变量
- 过程按值传参,被调过程中对参数的操作不改变调用过程中的参数,但是对于对象成员的改变是会影响的。eg: 在被调过程中传参x并执行 x . f = 3 x.f=3 x.f=3,在调用程序中也会有改变
- 可以 r e t u r n return return多个值
-
循环不变式的三条性质
- 初始(Initialization):第一次迭代前为真
- 保持(maintenance):循环某次的迭代之前为真,那么下一次迭代前也为真
- 终止(termination):循环终止时,看看最后的输出和相关状态是否正确,借此证明算法正确性
-
插入排序(见习题2.1-2)
-
归并排序(归并步骤常见写法见习题2.3-2)
//哨兵法 MERGE(A, p, q, r) //归并有序数组A[p..q] 与 A[q+1..r] n1 = q-p+1 n2 = r-q let L[1..n1+1] and R[1..n2+1] be new arrays for i = 1 to n1 L[i] = A[i+p-1] for j = 1 to n2 R[j] = A[q+j] i = 1 j = 1 L[n1+1] = INF R[n2+1] = INF for k = p to r if L[i] <= R[j] A[k] = L[i] i = i+1 else A[k] = R[j] j = j+1
MERGE-SORT(A, p, r) q = (p+r) / 2 MERGE-SORT(A, p, q) MERGE-SORT(A, q+1, r) MERGE(A, p, q, r)
2. 课堂笔记(Lecture 1)
3. 部分练习与思考题解答
章节练习题
2.1-2
Rewrite the INSERTION-SORT procedure to sort into non-increasing instead of non-decreasing order.
solve:
INSERTION-SORT(A)
for j = 2 to A.length
key = A[j]
i = j-1
while i>0 and A[i] < key //非降序则是 A[i] > key,此外无区别
A[i+1] = A[i]
i = i-1
A[i+1] = key
2.1-3
Consider the searching problem:
Input: A sequence of
n
n
n numbers
A
=
⟨
a
1
,
a
2
,
.
.
.
,
a
n
⟩
A= \langle a_1, a_2, ..., a_n \rangle
A=⟨a1,a2,...,an⟩and a value
v
v
v.
Output: An index
i
i
i such that
v
=
A
[
i
]
v = A[i]
v=A[i] or the special value NIL if
v
v
v does not appear in A.
Write pseudocode for linear search, which scans through the sequence, looking for
v
v
v. Using a loop invariant, prove that your algorithm is correct. Make sure that your loop invariant fulfills the three necessary properties.
solve:
LINEAR-SEARCH(A, v)
for j = 1 to A.length
if v == A[j]
return j
return NIL
用循环不变式证明算法的正确性(Sample Answer):
2.1-4
Consider the problem of adding two n-bit binary integers, stored in two n-element
arrays
A
A
A and
B
B
B. The sum of the two integers should be stored in binary form in an (n+1)-element array
C
C
C. State the problem formally and write pseudocode for adding the two integers.
solve:
ADD_TWO_NUMS(A, B, C, n)
carry = 0 //进位记录
for i = n downto 1
C[i+1] = (A[i] + B[i] + carry) % 2
carry = (A[i] + B[i] + carry) / 2
C[i+1] = carry
Sample Answer:
2.2-2
solve:
SELECTION-SORT(A, n)
for i = 1 to n-1
min_i = i
minval = A[i]
for j = i+1 to n
if minval > A[j]
minval = A[j]
min_i = j
swap(A[i], A[min_i]) //交换值
Loop Invariant: the subarray
A
[
1...
i
−
1
]
A[1...i-1]
A[1...i−1] consists of the smallest
i
−
1
i-1
i−1 elements with non-descending order.
Reason for n-1: when n-1 elements is the smallest, then the last element is the nth smallest.So the array is ordered.
best-case:
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)
worst-case:
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)
2.3-2
solve:
MERGE(A, p, q, r) //归并有序数组A[p..q] 与 A[q+1..r]
n1 = q-p+1
n2 = r-q
let L[1..n1] and R[1..n2] be new arrays
for i = 1 to n1
L[i] = A[i+p-1]
for j = 1 to n2
R[j] = A[q+j]
i = 1
j = 1
k = 1
while i<=n1 and j<=n2
if L[i] <= R[j]
A[k] = L[i]
i = i+1
else
A[k] = R[j]
j = j+1
k = k+1
while i<=n1
A[k] = L[i]
i = i+1
k = k+1
while j<=n2
A[k] = R[j]
j = j+1
k = k+1
2.3-5
iterative:
BINARY-SEARCH(A, l, r, v) // find key v in A[l..r] (assume A[l..r] is non-descending)
while l <= r
mid = (l + r) / 2
if v == A[mid]
return mid
else if v > A[mid]
l = mid+1
else
r = mid-1
return NIL // not found
recursive:
BINARY-SERACH(A, l, r, v)
if l > r
return NIL
mid = (l + r) / 2
if A[mid] == v
return mid
else if A[mid] > v
return BINARY-SEARCH(A, l, mid-1, v)
else
return BINARY-SEARCH(A, mid+1, r, v)
2.3-6
2.3-7★
//排序 + 双指针,如果返回位置的话还需要散列表记录元素位置,这里假定只返回数字值
TWO-SUM(S, n, x)
if n <= 0
return NIL
MERGE-SORT(S, 1, n) // sort the S[1..n] with non-descending order -> Θ(nlgn)
i = 1
j = n
while i < j //Θ(n)
sum = S[i] + S[j]
if sum == x
return {S[i], S[j]}
else if sum < x
i = i+1
else
j = j-1
return NIL
章末思考题
几个重要的题目用红色星星标记了一下,供重点回顾(其实思考题都很重要),其中标记的伪代码最好有时间实现一下。
2-1 Insertion sort on small arrays in merge sort
solve:
a.
n
/
k
×
Θ
(
k
2
)
=
Θ
(
n
k
)
n/k \times \Theta(k^2) = \Theta(nk)
n/k×Θ(k2)=Θ(nk)
b.
n
/
k
n/k
n/k个序列两两归并,归并次数为
Θ
(
l
g
(
n
/
k
)
)
\Theta(lg(n/k))
Θ(lg(n/k)) ,每层归并都需要比较n次(有n个元素),故为
Θ
(
n
l
g
(
n
/
k
)
)
\Theta(nlg(n/k))
Θ(nlg(n/k))
c. 标准归并算法:
Θ
(
n
l
g
n
)
\Theta(nlgn)
Θ(nlgn),改进的归并算法:
Θ
(
n
k
+
n
l
g
(
n
/
k
)
)
\Theta(nk +nlg(n/k))
Θ(nk+nlg(n/k))
要使运行时间相同,则有下面等式:
Θ
(
n
l
g
n
)
=
Θ
(
n
k
+
n
l
g
(
n
/
k
)
)
\Theta(nlgn) = \Theta(nk +nlg(n/k))
Θ(nlgn)=Θ(nk+nlg(n/k)),那么有
Θ
(
n
(
k
−
l
g
k
)
)
≤
Θ
(
n
l
g
n
)
\Theta(n(k-lgk)) \le\Theta(nlgn)
Θ(n(k−lgk))≤Θ(nlgn),即
Θ
(
k
−
l
g
k
)
≤
Θ
(
l
g
n
)
\Theta(k-lgk) \le \Theta(lgn)
Θ(k−lgk)≤Θ(lgn)
可见
k
=
Θ
(
l
g
n
)
k = \Theta(lgn)
k=Θ(lgn)时满足不等式
d. 确保k长度下的数组排序时,插排比二路归并排序快就行了
★2-2 Correctness of bubblesort
solve:
a. 还需要证明有序序列
A
′
A^\prime
A′的组成元素与
A
A
A相同,避免程序执行过程中有元素丢失
b. 直接贴答案了(对应第二层循环)
c. loop invariant: 序列
A
[
1..
i
−
1
]
A[1..i-1]
A[1..i−1]为非降序序列,且序列
A
[
i
.
.
n
]
A[i..n]
A[i..n]为
A
A
A的剩余元素组成的序列
证明正确性:
- 初始: i = 1 i=1 i=1,此时序列为空,自然成立
- 保持: i > 1 i>1 i>1时, A [ 1.. i − 1 ] A[1..i-1] A[1..i−1]非降序,第二层循环将从最末位至第i位的元素依次比较,并将最小者置换到第i位置, i = i + 1 i=i+1 i=i+1,由此下次循环前 A [ 1.. i ] A[1..i] A[1..i]为非降序
- 终止: i = A . l e n g t h i=A.length i=A.length,此时 A [ 1.. A . l e n g t h − 1 ] A[1..A.length-1] A[1..A.length−1]已为非降序序列,那么 A [ A . l e n g t h ] A[A.length] A[A.length]即 A [ 1.. A . l e n g t h ] A[1..A.length] A[1..A.length]中的最大者,因此 A [ 1.. A . l e n g t h ] A[1..A.length] A[1..A.length]也是非降序序列,由此冒泡排序算法正确。
d. Θ ( n 2 ) \Theta(n^2) Θ(n2),与插排一致
★2-3 Correctness of Horner’s rule
ANSWER
a. Θ ( n ) \Theta(n) Θ(n)
b. 一项项的求和就好,运行时间 Θ ( n 2 ) \Theta(n^2) Θ(n2),显然会比题示方法慢。
★c,d直接上答案(循环不变式的证明还是不熟悉呀):
★2-4 Inversions
ANSWER
a. (1,5), (2,5), (3,4), (3,5),(4,5)
b. 降序序列逆序对最多: C n 2 = n ( n − 1 ) / 2 C_n^2 = n(n-1)/2 Cn2=n(n−1)/2
★c.
不严格证明:
0个逆序对时插排时间为
Θ
(
n
)
\Theta(n)
Θ(n),此时每趟只有一次比较,没有移动
1个逆序对时插排最好情况为
Θ
(
n
+
1
)
\Theta(n+1)
Θ(n+1),有一次移动操作;最坏情况为
Θ
(
n
+
n
)
\Theta(n+n)
Θ(n+n)
C
n
2
C_n^2
Cn2个逆序对时插排对应最遭情况,
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)
对于
k
k
k个逆序对,那么运行时间为
Θ
(
n
+
k
)
\Theta(n+k)
Θ(n+k)
官方证明:
★d. 与答案有些许不同,可以找时间写个具体代码验证一下
CROSS-INVERSION-COUNTS(A, p, q, r) //题干中说明了元素各不相同
if p>=r
return 0
n1 = q-p+1
n2 = r-q
let L[1..n1] and R[1..n2] be new arrays
for i = 1 to n1
L[i] = A[i+p-1]
for j = 1 to n2
R[j] = A[q+j]
i = 1
j = 1
k = 1
count = 0 //inversion counter
while i<=n1 and j<=n2
if L[i] <= R[j]
A[k] = L[i]
i = i+1
else
count = count + j
A[k] = R[j]
j = j+1
k = k+1
while i<=n1
count = count + n2
A[k] = L[i]
i = i+1
k = k+1
while j<=n2
A[k] = R[j]
j = j+1
k = k+1
return count
CALC-INVERSION-COUNTS(A, p, r)
q = floor((p+r) / 2)
lcnt = CALC-INVERSION-COUNTS(A, p, q)
rcnt = CALC-INVERSION-COUNTS(A, q+1, r)
mcnt = CROSS-INVERSION-COUNTS(A, p, q, r)
return lcnt + mcnt + rcnt
//execute in main
CALC-INVERSION-COUNTS(A, 1, n)