• 算法导论课后习题解析 第二章


    2.1-1
    初始 31 41 59 26 41 58
    第一遍 31 41 59 26 41 58
    第二遍 31 41 59 26 41 58
    第三遍 26 31 41 59 41 58
    第四遍 26 31 41 41 59 58
    第五遍 26 31 41 41 58 59


    2.1-2
    把顺序改成非递增只要把判断大小时的条件改成小于即可

    Insertion-Sort(A)
    for j = 2 to A.length
    	key = A[j]
    	i = j - 1
    	while i > 0 and A[i] < key
    		A[i + 1] = A[i]
    		i = i - 1
    	A[i + 1] = key
    


    2.1-3

    Linear-Search(A, v)
    for i = 1 to A.length
    	if A[i] == v
    		return i
    return NIL
    
    • Initialization: 一开始i=1,说明子序列[1, 0]即空集中不包含所找的v。
    • Maintenance: 接下来,每次循环开始的时候都有子序列[1, i - 1]中不包含所找的v,而每次循环都判断A[i]是否是v,当不是v的时候说明子序列[1, i]中都不包含要找的v,这为下一次循环提供了前提条件。
    • Termination: 最后当循环退出的时候有两种情况:当发现A[i]等于v的时候返回要找元素的下标i;当循环进行到i=A.length次后,这时说明子序列[1, A.lenght]即整个序列中都没有要找的v,这个时候返回NIL。


    2.1-4
    Input: 两个n比特序列 $A = \{ a_1, a_2, …, a_n \}$ $B = \{ b_1, b_2, …, b_n \}$
    Output: 一个n+1比特序列 $C = \{ c_1, c_2, …, c_{n+1} \}$ 使得等式 $(c_{n+1} c_n…c_2 c_1)_2 = (a_n a_{n-1}…a_2 a_1)_2 + (b_n b_{n-1}…b_2 b_1)_2$ 成立
    利用全加器的逻辑位运算可以简单得到每一位的结果和进位。

    Binary-Addition(A, B)
    	inc = 0
    	i = 1
    	while i <= A.length
    		k = A[i] xor B[i]
    		g = A[i] and B[i]
    		C[i] = k xor inc
    		inc = inc and k or g
    		i = i + 1
    	C[i] = inc
    	return C
    


    2.2-1
    $\Theta (n^3)$


    2.2-2

    Selection-Sort(A)
    	for i = 1 to A.length - 1
    		min = i
    		for j = i + 1 to A.length
    			if A[j] < A[min]
    				min = j
    		tmp = A[min]
    		A[min] = A[i]
    		A[i] = tmp
    
    • Initialization: 一开始i=1,说明子序列[1, 0]即空集有序。
    • Maintenance: 接下来,每次循环开始的时候都有子序列[1, i - 1]都是有序的,且[1, i - 1]中的元素都小于[i, A.length]中的元素。而每次循环都把[i, A.length]中最小的元素与A[i]交换,由于A[i]大于[0, i - 1]中的任意元素,所以[0, i]中的元素有序,且[0, i]中的元素都小于[i + 1, A.length]中的元素,这为下一次循环提供了前提条件。
    • Termination: 最后当循环退出的时候[1, A.length - 1]中的元素有序,且都小于A[A.length],所以整个[1, A.length]序列有序。
    由于循环的次数与输入无关,所以最好与最坏的时间复杂度都是 $\Theta (n^2)$


    2.2-3
    第i个元素为所找元素需要的比较次数为i
    如果每个元素被找的几率相同且必定查找成功的时候,平均比较次数 $c = (1 + 2 + ... + n)/n = (n+1)/2$
    当元素不存在或者是最后一个元素的时候需要比较的次数最多,为n次。
    平均和最坏时间复杂度都是 $\Theta (n)$


    2.2-4
    当输入有可能就是结果(比如排序)或者能够直接算出的情况(比如全零矩阵相乘),直接输出结果。


    2.3-1

           [3 9 26 38 41 49 52 57] 
       [3 26 41 52]       [9 38 49 57]
     [3 41]   [26 52]   [38 57]   [9 49]
    [3] [41] [52] [26] [38] [57] [9] [49]
    


    2.3-2

    Merge(A, p, q, 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[p + i - 1]
    	for j = 1 to n2
    		R[i] = A[q + j]
    	i = 1, j = 1, k = p
    	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-3
    当n=2时, $T(n) = 2 * \lg 2 = 2$ 满足;
    假设当n=k时有 $T(k) = k * \lg k $ ,
    那么当n=2k时有
    $\eqalign{ T(2k) & = 2 T(k) + 2k \\ & = 2k \lg k + 2k \\ & = 2k \lg k + 2k \lg 2 \\ & = 2k \lg {2k} }$
    得证。


    2.3-4

    Insertion-Sort(A, n)
    	if n > 1
    		Insertion-Sort(A, n - 1)
    		i = n - 1
    		key = A[n]
    		while i > 0 and A[i] > key
    			A[i + 1] = A[i]
    			i = i - 1
    		A[i + 1] = key
    


    2.3-5

    Binary-Search(A, v)
    	low = 1, high = A.length
    	while low <= high
    		mid = (low + high) / 2
    		if A[mid] == v
    			return mid
    		else if A[mid] < v
    			low = mid + 1
    		else
    			high = mid - 1
    	return NIL
    
    假设循环开始前序列的长度为k,循环之后长度至多为 $\lfloor k/2 \rfloor$,每次比较大小花费常数时间,
    所以该算法花费时间的递推公式为 $T(n) = T(\lfloor n/2 \rfloor) + \Theta (1)$,得出时间复杂度为 $T(n) = \Theta (\lg n)$


    2.3-6
    不可以,因为虽然每次查找的时间复杂度为 $\Theta (\lg n)$ 但是把元素放到正确的位置仍然需要移动同样多的元素,所以复杂度没有变化,在n比较大的时候系数可能会变小一点。


    2.3-7

    Sum-Exist(A, x)
    	Merge-Sort(A)
    	low = 1, high = A.length
    	while low < high
    		sum = A[low] + A[high]
    		if sum == x
    			return true
    		else if sum > x
    			high = high - 1
    		else
    			low = low + 1
    	return false
    
    • Initialization: 一开始将序列非递减排序,有$a_1 \le a_2 \le \cdots \le a_n$,此时low=1,high=n,必然有: $$\forall i \in [1, low-1], j \in [low, high] 都有a_i + a_j < x \tag{1}$$ $$\forall i \in [high+1, n], j \in [low, high]都有a_i + a_j > x \tag{2}$$ $$\forall i \in[1, low-1], j \in [high+1, n]都有 a_i + a_j \not = x \tag{3}$$
    • Maintenance: 接下来,每次循环都判断$sum = a_{low} + a_{high}$的大小。
      当$sum < x$时,说明 $\forall i \in [low+1, high]都有a_{low} + a_i < x$,这时将low增1使得式(1)得到满足,从而使式(3)得到满足,式(2)不变;
      当$sum > x$时,说明 $\forall i \in [low, high-1]都有a_{high} + a_i > x$,这时将high减1使得式(2)得到满足,从而使式(3)得到满足,式(1)不变;这为下一次循环提供了前提条件;
    • Termination: 最后当循环退出的时候有两种情况。
      当判断$sum = x$时,说明找到了元素$a_i, a_j使得a_i + a_j = x$;
      当判断$low = high$时令$k=low=high$,由式(1)可得 $\forall i \in[1, k-1]都有 a_i + a_{k} < x$,由式(2)可得 $\forall i \in [k+1, n]都有a_i + a_{k} > x$,由式(3)可得 $\forall i \in[1, k-1], j \in [k+1, n]都有 a_i + a_j \not = x$
      所以不存在这样的两个数。

    复杂度分析:第一步归并排序的时间复杂度为$O(n \lg n)$,之后最坏的情况是没找到这样的两个数,需要对每个元素比较一次,时间复杂度为$O(n)$,所以总的时间复杂度为$O(n \lg n)$。


    2-1
    a. 每个子序列的元素数量为k,最差的情况下需要进行$k(k-1)/2$次基础操作,一共有$n/k$个子序列,所以总的基础操作次数为 $$\frac {n} {k} * \frac {k(k-1)} {2} = \frac {n(k-1)} {2} = \Theta (nk)$$ b. 每一遍归并都对所有元素进行拷贝,所以单次复杂度为$\Theta (n)$,而将n/k组子序列归并为一个序列最多需要$\lg {\lfloor n/k \rfloor}$次,所以总的时间复杂度为$\Theta (n \lg {n/k} )$
    c. 由前两问的结果可知,复杂度为$\Theta (nk + n \lg {n/k})$。要找到最大的k就应该使得$\Theta (nk)$和$\Theta (n \lg {n/k} )$的增长率相等,这时$k = \lg n$,即
    $$\Theta (n \lg n + n \lg {\frac {n} {\lg n}}) = \Theta (2n \lg n - n \lg {\lg n}) = \Theta (n \lg n)$$ d. 实际应用上k应该选取使得插入排序和归并排序运行时间相同时的输入规模,充分发挥插入排序常数系数小的优势。


    2-2
    a. 需要证明最终的序列是非递减的,还需要证明最终序列是原序列的一种排列。
    b.

    • Initialization: 一开始j=A.length,有A[j]不大于子序列[j, A.length]中的元素;
    • Maintenance: 接下来,每次循环开始的时候都有A[j]不大于子序列[j, A.length]中的元素。而每次循环都把A[j]和A[j-1]中较小的元素放到A[j-1],则有A[j-1]不大于子序列[j-1, A.length]中的元素,然后让j减1,满足了循环初始的条件,由于只做了比较和交换操作,所以序列[i, A.length]只是原来序列的一种排列。
    • Termination: 最后当循环退出的时候有A[i]不大于子序列[i, A.length]中的元素,即A[i]是子序列[i, A.length]中最小的之一。
    c.
    • Initialization: 一开始i=1,[1, i-1]中的元素非递减,且[i, A.length]中的元素都不小于[1, i-1]中的元素;
    • Maintenance: 接下来,每次循环开始的时候都有[1, i-1]中的元素非递减,且[i, A.length]中的元素不小于[1, i-1]中的元素。而每次循环都把A[i]和[i, A.length]中最小的之一交换,又A[i]不小于[1, i-1]中的元素,所以[1, i]中的元素非递减,且[i+1, A.length]中的元素都不小于[1, i]中的元素,这时i增加1,满足了循环初始的条件。
    • Termination: 最后当循环退出的时候有[1, A.length-1]中的元素非递减,且A.length不小于[1, i-1]中的元素。所以整个序列[1, A.length]非递减。
    d. 最差的情况下,冒泡排序(bubblesort)需要进行$n(n-1)/2$次比较,所以时间复杂度为$\Theta (n^2)$和插入排序一致;在最好的情况下,比较的次数不变,而插入排序比较的次数减少为n-1次,总体来说效率比插入排序差。


    2-3
    a. 循环中的运算可以在常量时间内完成,所以复杂度是$\Theta (1)$,循环次数为n,所以总的时间复杂度为$\Theta (n)$。
    b.

    Evaluate-Polynomial(A, x, n)
    	y = 0
    	for k = 0 to n
    		t = 1
    		for i = 1 to k
    			t = t * x;
    		y = y + A[k] * t
    	return y
    
    两重循环内的乘法运算次数最多,总数为 $$\sum_{k=0}^n {k} = \frac {n(n-1)} {2}$$ 所以时间复杂度为$\Theta (n^2)$,效率低于霍纳法则(Horner's Rule)。
    c. 当循环进行到最后一次时,i = 0,可得 $$y = a_0 + x \sum_{k=0}^{n-(0+1)} {a_{k+0+1} x^k} = a_0 + \sum_{k=0}^{n-1} {a_{k+1} x^{k+1}} = \sum_{k=0}^n {a_k x^k}$$ 得到所求
    d. 根据c的循环不变式可以保证该算法所得结果正确。


    2-4
    a. (8, 6) (2, 1) (3, 1) (8, 1) (6, 1)
    b. 拥有逆序数最多的是 $\{n, n-1, \cdots, 3, 2, 1 \}$,其中任意两个数之间都是逆序的,所以总计$n(n-1)/2$个。
    c. 逆序数应该与元素移动的数量相同。插入排序每次移动元素都将一个较大的数移到较小的数后面,使得总逆序数减少1,而排序完成后逆序数是0,所以开始的逆序数和元素移动的次数相同。
    d.

    Merge-Inversion(A, p, q, 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[p + i - 1]
    	for j = 1 to n2
    		R[i] = A[q + j]
    	i = 1, j = 1, k = p, inv = 0
    	while i <= n1 and j <= n2
    		if L[i] <= R[j]
    			A[k] = L[i]
    			i = i + 1
    		else
    			inv = inv + n1 - i + 1
    			A[k] = R[j]
    			j = j + 1
    		k = k + 1
    	while i v= n1
    		A[k] = L[i]
    		i = i + 1
    		k = k + 1
    	while j <= n2
    		A[k] = R[j]
    		j = j + 1
    		k = k + 1
    	return inv
    
    Calculate-Inversion(A, p, r)
    	inv = 0
    	if p < r
    		q = (p + r) / 2
    		inv = inv + Calculate-Inversion(A, p, q)
    		inv = inv + Calculate-Inversion(A, q, r)
    		inv = Merge-Inversion(A, p, q, r)
    	return inv
    
    利用归并排序,先算出每个子序列的逆序数并排序,使得子序列的逆序数为0,然后再归并成更大的序列并重复计算,最后所有子序列的逆序总和为所求。
    在Merge过程中如果R序列中的元素$r_j$出现在L中的元素$l_i$之前,说明$r_j$与$[l_i, l_{n1}]$中的所有元素都组成逆序数,一共有n1 - i + 1个。


  • 相关阅读:
    【java】详解java多线程
    【java】switch case支持的6种数据类型
    【Java】详解java对象的序列化
    【java】详解I/O流
    【java】自定义异常类
    【java】详解集合
    【NotePade++】NotePade++如何直接编译运行java文件
    【java】JVM的内存区域划分
    Unicode和UTF的关系
    【java】解析java中的数组
  • 原文地址:https://www.cnblogs.com/Jiajun/p/3055460.html
Copyright © 2020-2023  润新知