第二章、思考题
答:

a.

插入排序的最坏时间复杂度为Θ(n2)\Theta(n^2),对一个长度为kk的子表进行排序需要最坏需要Θ(k2)\Theta(k^2)时间,那么排序n/kn/k个子表需要Θ(nk)\Theta(nk)的时间。

b.

归并排序先对数组进行对半拆分,需要拆分Θ(log2n)\Theta(log_2n)次,现在当拆分到数组的长度为kk时,停止拆分,然后使用插入排序对这n/kn/k个子数组进行排序,然后归并排序开始合并,所以拆分的次数从Θ(log2n)\Theta(log_2n)减少至Θ(log2n)\Theta(log_2n) - Θ(log2k)\Theta(log_2k) = Θ(log2n/k)\Theta(log_2{n/k}),合并的时间复杂度为Θ(n)\Theta(n),所以总时间复杂度为Θ(nlog2n/k)\Theta(nlog_2{n/k})

c.
若要使修改后具有相同的运行时间,需要Θ(nk+nlg(n/k))=Θ(nlgn)\Theta(nk + nlg(n/k)) = \Theta(nlgn),得到lgk=klgk = k,所以0<k<10<k<1,当k=1k=1时,修改后的算法运行时间为Θ(n+nlgn)\Theta(n + nlgn),也就是只要引入插入排序,其运行时间总要大于标准的归并排序(这里是忽略了常数因子还有机器的影响)。但如果借助Θ\Theta记号,那么只要kk是常数,那么Θ(nk+nlg(n/k))\Theta(nk + nlg(n/k))Θ(nlgn)\Theta(nlgn)的渐进性是相同的。

d.
在实践中,该数的大小跟被Θ\Theta记号隐藏的常数因子的大小与机器的影响有关,而且该数应该是个常数,所以可以尝试各种kk的数值,然后对比其运行时间,通过足够多的尝试来决定kk的数值。

第二章、思考题

答:

a.

我们需要证明A=AA'=A

b.

第2 ~ 4行代码的作用是把小的元素与上一个元素交换,直到循环终止,最小的元素在A[i]A'[i]的位置上。

  1. 初始化:在未进行迭代之前,最小元素最多位于A[n]A'[n]上,该结论为真。
  2. 保持:在每次迭代,如果当前元素比上一个元素小,便交换其位置,如果大,便什么都不做。所以在第kk次迭代之后,都可以确定最小元素的位置至多在[nk]'[n-k]
  3. 终止:在循环结束时,也就是j=i+1j=i+1时,此时k=nik=n-i,最小元素在A[i]A'[i]的位置上。

c.

  1. 初始化:在迭代之前,子数组为A[1...i1]A'[1...i-1],此时子数组包含0个元素。
  2. 保持:在第kk次迭代后,根据b的结论,第kk小的元素会在A[k]A'[k]的位置上,所以在第k+1k+1次迭代之前,子数组A[1...k]A'[1...k]是有序的,然后此次迭代之后,子数组A[1...k+1]A'[1...k+1]是有序的。
  3. 终止: 当i=n1i=n-1时,循环终止,此时子数组A[1...n1]A'[1...n-1]是有序的,且A[n1]A'[n-1]A[n]A'[n]小,所以该数组有序。

d.

冒泡排序中,2 ~ 4行代码无论如何都需要进行nin-i次循环,所以时间复杂度为Θ(n)\Theta(n),而且必需进行nn次循环,所以他的时间复杂度为Θ(n2)\Theta(n^2)。冒泡排序是稳定性的排序,其最坏时间复杂度与最好时间复杂度都为Θ(n2)\Theta(n^2)。而插入排序的最好时间复杂度为Θ(n)\Theta(n)

第二章、思考题

答:

a.

实现以上代码片段的运行时间为Θ(n)\Theta(n)

b.
y=0for i=0 downto n       y=y+aixi \begin{aligned} &y = 0\\ &for\ i = 0\ downto\ n\\ &\ \ \ \ \ \ \ y = y + a_i*x^i \end{aligned}
循环内部需要对xxii次方,也就是每次循环需要i+1i+1次乘法跟一次加法,一共要进行n+1n+1次循环,所以其运行时间为Θ(n2)\Theta(n^2)。与霍纳规则相比,朴素算法的每次迭代都要比霍纳规则多ii次乘法,因为朴素算法要求xix^i,所以其性能要比霍纳算法差得多。

c.

**初始化:**当i=ni=n时,n(n+1)=1n-(n+1)=-1,所以y=0y=0
**保持:**在本次的循环开始,有:
y=k=0n(i+1)ak+i+1xk y=\sum_{k=0}^{n-(i+1)}a_{k+i+1}x^k
当本次循环结束时:
y=ai+xk=0n(i+1)ak+i+1xk=ai+xk=1niak+ixk1=k=0niak+ixk y=a_i+x\sum_{k=0}^{n-(i+1)}a_{k+i+1}x^k = a_i+x\sum_{k=1}^{n-i}a_{k+i}x^{k-1} = \sum_{k=0}^{n-i}a_{k+i}x^{k}
当下次循环开始时,有i=i1i=i-1,有
y=k=0niak+ixk=k=0n(i1+1)ak+(i1+1)xk y=\sum_{k=0}^{n-i}a_{k+i}x^{k} = \sum_{k=0}^{n-(i-1+1)}a_{k+(i-1+1)}x^{k}
所以在下次循环开始时,也有:
y=k=0n(i+1)ak+i+1xk y=\sum_{k=0}^{n-(i+1)}a_{k+i+1}x^k
**终止:**当循环终止时,有i=1i=-1,此时
y=k=0nakxk y=\sum_{k=0}^{n}a_{k}x^{k}

d.

c中的结论可以看出该代码片段是正确的。

第二章、思考题

a.

(2,1)(3,1)(8,1)(6,1)(8,6)(2,1)、(3,1)、(8,1)、(6,1)、(8,6)

b.

当数组是一个递减的数组时具有最多的逆序对,具有n(n1)/2n(n-1)/2

c.

第二章、思考题

从插入排序的伪代码中可以看出,插入排序的运行时间取决于5~7行代码的循环次数,在每次进行while循环时,只要前面有多少个数比当前的key大,便要进行多少次循环,所以每次循环的次数是子数组A[1...j]A[1...j]的逆序对的个数。所以while循环总的运行时间为Θ(x),x\Theta(x),x为逆序对的个数,那么总插入排序总的运行时间为Θ(n+x)\Theta(n + x)

d.

 	private static int getPairCountEachPart(int[] a, int p, int q, int r, int count) {
		int[] left = new int[q - p + 1];
		int[] right = new int[r - q];
		
		for (int i = 0; i < left.length; i++) {
			left[i] = a[p + i];
		}
		
		for (int j = 0; j < right.length; j++) {
			right[j] = a[q + j + 1];
		}
		int i = 0, j = 0;
		while (p <= r) {
			if (left[i] <= right[j]) {
				a[p++] = left[i++];
				if (i == left.length) {
					for (;j < right.length; j++) {
						a[p++] = right[j];
					}
					break;
				}
			} else {
				a[p++] = right[j++];
				count += left.length - i;
				if (j == right.length) {
					for (;i < left.length; i++) {
						a[p++] = left[i];
					}
					break;
				}
			}
		}
		return count;
	}
	
	public static int getSequencePairCount(int[] a) {
		return getSequencePairCount(a, 0, a.length - 1);
	}
	
	public static int getSequencePairCount(int[] a, int p, int r) {
		int left = 0, right = 0;
		if (p < r) {
			int q = (p + r) / 2;
			left = getSequencePairCount(a, p, q);
			right = getSequencePairCount(a, q + 1, r);
	        if (a[q] <= a[q + 1]) {
	            return left + right;
	        }
			return getPairCountEachPart(a, p, q, r, left + right);
		}
		return 0;
	}

相关文章: