1. 程式人生 > >演算法導論習題解答Chapter2(學習筆記)

演算法導論習題解答Chapter2(學習筆記)

目錄

2.1-1

2.1-2

2.1-3

2.1-4

2-4:

Chapter2

2.1-1

以升序排列為例

首先取出陣列中的第二個元素,然後和這個元素的前面的元素組成的子陣列從子陣列中的最後一個元素開始比較,如果該元素大於待插入的元素,則將該元素向後移動一位,然後將帶插入的元素與之前的元素比較,迴圈往復,如果待插入的元素大於比較的元素,則退出移動陣列元素的操作,此時將帶插入的元素插入到這個比它小的元素的後面,即之前比帶插入元素大的元素的前面,此時完成了該待插入元素的插入,然後將第三個元素取出來重複上面的步驟,直到將陣列最後一個元素插入即可結束。

2.1-2

public static void insertionSort_desc(int[] arr) {
		for(int j = 1; j<arr.length; j++) {
			int key = arr[j];//取出待插入的元素
			int i = j-1;//子陣列的最後一個元素的下標(已排序好的陣列)
			while(i>=0 && arr[i]<key) {//尋找插入的合適位置
				arr[i+1] = arr[i];
			}
			arr[i+1] = key;//插入到合適位置
		}
	}

2.1-3

虛擬碼如下

FIND_VALUE(v, A)  //v表示要查詢的數, A表示查詢的陣列
    for i=1 to A.length:
        if v == A[i]:
            break
    if i <= A.length:
        return i
    else:
        v = NIL
        return v

下標i指出要比較的元素的位置,每次迴圈開始前,包含元素A[1,...,i-1]元素的子陣列中不存在和v相等的值,而子陣列A[i+1,...,A.length]  中是還未比較的數,當取出最後一個元素之後,當i = A.length+1 或者當A[i]  == v 時 ,退出迴圈,那麼退出迴圈後,如果 i <= A.length 那麼就說明在陣列中找到了和v相等的值,否則就是沒有找到, 讓v = NIL

證明過程如下:

初始化:當第一次迴圈開始之前,即i = 0, 子陣列A[1,0]不存在,所以子陣列中沒有值和v相等

保持:如果當第i迴圈之前,在子陣列A[1,i-1]中不存在和v相等的值,那麼當迴圈結束後,子陣列A[1,i-1]中仍然沒有和v相等的值

結束:如果在陣列中找到了和v相等的值,那麼退出迴圈,此時A[i] == v, 並且肯定是第一個和v相等的值,那麼A[1,i-1] 中一定沒有和v相等的值。如果在陣列中沒有找到和v相等的值,那麼當退出迴圈之前, i = A.length+1 那麼此時在子陣列A[1,A.length]中沒有和v相等的值

綜上,迴圈不變式成立

2.1-4

演算法描述如下:

輸入:兩串陣列用來表示兩個二進位制形式的整數,並且兩個陣列的長度相等,都是n

輸出:一串長度是n+1的陣列

虛擬碼如下:

ADD_BINARY(A,B) //A,B表示要加的兩個長度是n的二進位制數
    C = new int[n+1] ; //建立長度是n+1的陣列
    carry  = 0 ;    // 表示進位
    for i=1 to A.length:
        C[i] = (A[i]+B[i]+carry)%2
        carry = (A[i] + B[i] + carry) /2
    C[i] = carry
    return C

2.2-1:

theta(n^3)

2.2-2:

虛擬碼如下:

//降序排列
Select-Sort(A):
    for count = 1 to A.length-1   //待交換的元素的位置   
        min = count;//用來記錄每次選擇的最小元素         
        for i = count+1 to A.length:                     
            if A[min] > A[i]:                           
                min = i;                        
        temp = A[i];                             
        A[i] = A[min];
        A[min] = temp;

迴圈不變式:count指出將要交換的元素的位置, 在外層for迴圈每次迭代的開始, 子陣列A[1,count-1]中的元素是陣列中最小的元素,並且按照升序排列, 並且在內層每次迭代之前, min所指向的元素是子陣列A[count, i-1]中最小的元素

為什麼是A.length-1呢?

因為當最後一次迭代之後, count=A.length此時子陣列A[1, count-1]中的元素是最小的元素,而子陣列A[count, A.length]中只有一個元素並且一定比前面的都大,所以已經有序了

執行時間如下:

最好情況是原陣列已經有序,則為 theta(n^2)

最壞情況是原陣列反序, 則為theata(n^2)

2.2-3:

平均情況是可能有一半的元素會被訪問到, 即n/2,最壞情況是全部都訪問到, 即n, 執行時間都是theta(n)

2.3.1:

首先因為輸入規模大於1,所以先將原規模分解成兩個小的規模,然後對這兩個規模更小的陣列進行處理。如此往復, 只要輸入規模為1時,則進行合併操作, 將有序的兩個數組合併成一個有序的陣列

因為原陣列為:A{3, 41, 52, 26, 38, 57, 9, 49}

因為輸入規模大於1, 所以分解成兩個陣列, A1{3, 41, 52, 26},   B1{38, 57, 9, 49}             (1)

因為輸入規模仍然大於一,所以繼續分解, 此時先分解左邊的, A2{3, 41}, C2{52, 26}       (2)

因為輸入規模仍大於一,所以繼續分解,此時先分解左邊的, A3{3}, D3{41}

此時輸入規模為1,遞迴開始回升, 回到(2)的位置, 然後此時左邊的排序結束,A2{3, 41}, 開始處理右邊的

此時輸入規模大於一, 繼續分解...               C3{52}, E3{26}

此時輸入規模為1, 遞迴開始回升, 回到(2) 的位置, 此時C2{26, 52} 已經有序, 開始合併A2和 C2, 遞歸回升到(1)處

此時A1{3, 26, 41, 52} 已經有序, 開始處理右邊的B1

此時輸入規模大於1一 , 分解後,   B2{38, 57}, F2{9, 49}, (3)

此時輸入規模仍然大於一, 分解, B3{38}, G3{57},

此時輸入規模為1, 遞迴開始回升, 此時回到(3)處, B2{38, 57} 已經有序, 開始處理F2,

此時輸入規模大於一, 分解, F3{9},  H3{49},

此時輸入規模為1, 遞迴開始回升, 此時回到(3)處, 此時兩個陣列都已經有序了,開始回升到(1), 此時B1{9, 38, 49, 57}已經有序, 兩個陣列都已經有序, 所以繼續回升

降格A1和B1合併後, 原陣列就有序了

2,3-2:

java程式碼如下:

class MergeSortWithoutSentinel{
	private static void merge(int[] arr, int p, int q, int r) {
		int n1 = q-p;
		int n2 = r-q;
		int[] arr1 = new int[n1];
		int[] arr2 = new int[n2];
		for(int i= 0; i< arr1.length; i++) {
			arr1[i] = arr[p+i];
		}
		for(int i = 0; i< arr2.length; i++) {
			arr2[i] = arr[q+i];
		}
		
		for(int k=p, i=0, j=0; k< r; k++) {
			if(i >= n1) {
				arr[k] = arr2[j];
				j++;
			}else if(j>=n2) {
				arr[k] = arr1[i];
				i++;
			}else if(arr1[i] <= arr2[j]) {
				arr[k] = arr1[i];
				i++;
			}else {
				arr[k] = arr2[j];
				j++;
			}
		}
	}
	
	public static void mergeSort(int[] arr, int p, int r){
		if(p< r-1) {
			int q = (p+r)/2;
			mergeSort(arr, p, q);
			mergeSort(arr, q, r);
			merge(arr, p, q, r);
		}
	}
}

2.3-5:

虛擬碼如下:

harfSearch(A, low, high, key):  //A已經升序排列
    if low > high:
        return NIL;
    mid = (low+high)/2;
    if A[mid] > key:
        harfSearch(A, low, mid-1, key);
    else if A[mid] < key:
        harfSearch(A, mid+1, high, key);
    else:
        return mid;

2-4:

a: (2,1),(3,1),(8,1),(6,1),(8,6)

b:降序陣列逆序對最多, 有(n-1)n/2對逆序對

c:逆序對越多, 插入排序降序的執行時間更長

d:計算給定的一組元素中的逆序對的個數:

merge(A, p, q, r):
    inversion = 0; //如果左邊的比右邊的大, 則算作一個逆序對, 通過這個變數記錄逆序對的個數
    n1 = q-p+1;
    n2 = r-q;
    let L[n1] and R[n2] to be new arrays;
    for i=1 to n1:
        L[i] = A[p+i-1];
    for i=1 to n2:
        R[i] = A[q+i];
    
    i = 1;
    j = 1;
    for k=p to r:
        if i>n1:
            A[k] = R[j];
            j++;
        else if j>n2:
            A[k] = L[i];
            i++;
        else if L[i] > R[j]:
            A[k] = L[i];
            i++;
            inversion += 1;
        else:
            A[k] = R[j];
            j++;
        return inversion;

mergeSort(A, p, r):
    inversion = 0;
    if p < r:
        q = (p + r)/2;
        inversion += mergeSort(A, p, q);
        inversion += mergeSort(A, q+1, r);
        inversion += merge(A,p, q, r);
        
    return inversion;