# 演算法作業 1 ###### tags: `演算法` 1. Prove that each of the following sorting algorithms is stable or show that it is unstable by giving a counter example; moreover, determine whether it is in place: bubble sort, insertion sort, quick sort, merge sort, heap-sort. | | Bubble Sort | Insertion Sort | Quick Sort | Merge Sort | Heap Sort | | -------- | -------- | -------- | -------- | -------- | -------- | | stable | Yes | Yes | No | Yes | No | | in place | Yes | Yes | Yes/No | No | Yes | ------------ * Bubble Sort `Input: A[n] ,n is the length of array A` ``` BubbleSort(A[]){ bool swap = false; for i = 1 : n swap = false; for j = 1 : n-i if(A[j]>A[j+1]) SWAP(A[j],A[j+1]); swap=true; if(!swap) break; } ``` 分析: 它是stable,在原本未排序的A當中值相同的元素,經過排序後相對位置不會被互換。 它是in-place,排序過程僅有一個布林變數swap被使用,這是唯一多使用的空間,大小是$O(n)$,因此是in-place。 * Insertion Sort ``` InsertionSot(A[]){ for i = 2 : n int key = A[i]; int j=i-1; while(j>0 and A[j]>key) A[j+1] = A[j]; j=j-1; A[j+1]=key; } ``` 分析: it's stable,A中任意兩相同大小的元素經過排序後相對位置不會改變。 it's in-place,整個排序過程只多開了一個變數key,大小是$O(n)。 * Quick Sort ``` QuickSort(A[],start,end){ if(start<end){ pivot = Partition(A[],start,end); QuickSort(A[],start,pivot-1); QuickSort(A[],pivot+1,end); } } Partition(A[],start,end){ int pivot = end; int i,j = start - 1; for i = start : end-1 if(A[i]<=A[pivot]) j=j+1; SWAP(A[j],A[i]); j=j+1; SWAP(A[j],A[pivot]); return j; } ``` 分析: it's not stable,假設A原本是{5,8(a),8(b)},經過排序後會變成{5,8(b),8(a)},原本相同大小的8(a),8(b)相對位置被改變了。 它可能是in-place也可能不是,如果排序過程每次挑到的pivot都是子陣列的最大或最小值,那加開的空間可能達到$O(n)$,這樣就不是in-place,如果大多數挑到的pivot都不是最大最小值,那加開的空間大約是$O(log(n))$,這樣是in-place。 * Merge Sort ``` MergeSort(A[],start,end){ if(start<end) int mid = floor((start+end)/2); MergeSort(A[],start,mid); MergeSort(A[],mid+1,end); Merge(A[],start,mid,end); } Merge(A[],start,mid,end){ if(start==mid) or (mid+1==end) return; int L[mid-start+2],R[end-mid+1]; L[mid-start+2]=inf; R[end-mid+1]=inf; int l=1,r=1; for i = start:end if(L[l]<=R[r]) A[i]=L[l]; l=l+1; else A[i]=R[r]; r=r+1; } ``` 分析: it's stable,任意兩個相同大小的值在merge的時候,前面的那個一定會先被放進排好的陣列中。 it's not in-place,每次merge都需要加開L[]、R[]兩個陣列,空間是$O(n)$,因此並非in-place。 * Heap Sort ``` HeapSort(A[]){ //Build a Max Heap for i = floor(A.length/2) to 1 Max-Heaplify(A[],i); n=A.size; for i = n to 2 SWAP(A[1],A[i]); A.size=A.size-1; Max-Heaplify(A[],1); } Max-Heaplify(A[],root){ int child = root*2; while(child<=A.size) if(child+1<=A.size && A[child]<A[child+1]) child=child+1; if(A[root]>=A[child]) break; SWAP(A[root],A[child]); root=child; child=root*2; } ``` 分析: it's not stable,假設有一陣列{8(a),8(b),5},經過heap sort之後會變成{5,8(b),8(a)},因此不是stable。 it's in-place,heap sort過程中只有加開$O(1)$大小的變數來幫助排序,因此為in-place。 2. Design a data structure to represent a set with elements being positive integers, and then design algorithms for the following operations: Compute the union of two sets. Compute the intersection of two sets. Determine if a given element is in a given set. Answer: 我設計的Set當中的元素在陣列當中是以升序排列的方式儲存。 ```cpp= class Set{ Element[];//元素陣列,已升序排列好 Union(Set A,Set B); Intersection(Set A,Set B); Exist(int num); } Intersection(Set A,Set B){ I[]={};//建立一個新的空陣列,用來儲存Union Set i=1; j=1; k=1; while(i<=A.size and j<=B.size){ if A[i]==B[j] I[k]=A[i]; k=k+1; while(A[i]==B[j]) i=i+1; while(A[i]>B[j]) j=j+1; while(A[i]<B[j]) i=i+1; } return I[]; } Union(Set A,Set B){ U[]={};//建立一個空陣列,用來儲存Intersection i=1; j=1; k=1; while(i<=A.size and j<=B.size){ if(A[i]==B[j]) U[k]=A[i]; k=k+1; while(A[i]==B[j]) i=i+1; while(A[i]>B[j]) U[k]=B[j]; j=j+1; k=k+1; while(A[i]<B[j]) U[k]=A[i]; i=i+1; k=k+1; } while(i<=A.size) U[k]=A[i]; k=k+1; i=i+1; while(j<=B.size) U[k]=B[j]; k=k+1; j=j+1; return U[]; } Exist(int num){ for i = 1 to Element.size if num==Element[i] return true; return false; } ``` 3. Given two sorted arrays $x[1]…x[m]$, $y[1]…y[n]$, design an algorithm to compute $min_{i, j} | x[i] − y[j]|$. Answer: Time complexity: $O(m+n)$ ```cpp= funct(x[],y[]){ if x[1]>=y[n] return abs(x[1]-y[n]); if y[1]>=x[n] return abs(y[1]-x[n]); i=1;j=1; min=abs(x[1]-y[1]); while(i<=m and j<=n){ dis=abs(x[i]-y[j]); if dis==0 return 0; if dis<min min=dis if x[i]>y[j] j=j+1; else i=i+1; } return min; } ``` 4. Solve the recurrence $T(n) = 2T(n/2) + n – 1$ where $n = 2^𝑘$ is assumed. $T(1) = 0$ Answer: ![](https://i.imgur.com/CXALpyK.jpg) 5. Given a set S of n integers, and another number M, we want to determine whether or not there exist 2 numbers in S whose sum is exactly M. The algorithm of testing all possible 2 numbers in S will take $O(𝑛^2)$ time and it is unacceptable. a) Design a more efficient algorithm for solving this problem. Analyze the time complexity of your algorithm. b) Extend your algorithm for the following case: determine whether or not there exist 3 numbers in S whose sum is exactly M. c) What about the case: determine whether or not there exist k ( > 3) numbers in S whose sum is exactly M. Answer: (a) dp版本 **初始值:** $Able_{i00}=true、Able_{i1(S_i)}=true、Able_{0jk}=false$ **遞迴式:** $Able_{ijk}=\begin{cases} Able_{(i-1)(j-1)(k-S_i)} \ or \ Able_{(i-1)jk} \ if \ k>=S_i\\ Able_{(i-1)jk} \ if \ k<S_i\\ \end{cases}$ **解答:** $Able_{n2M}$ **Time complexity:** $O(n*M)$ M可能大於等於n ```cpp= input:S[],n,2,M funct(S[],2,M){ Able[n][2][M]={false};//將陣列元素全部設為false //Able[i][j][k]代表從s[]的前i個元素中找j個組成k for i= 1:n Able[i][0][0]=true; Able[i][1][S[i]]=true; for i = 1:n for j = 1:i and j<=2 for k=1:M if(k>=S[i]) Able[i][j][k]=Able[i-1][j-1][k-S[i]] or Able[i-1][j][k]; else Able[i][j][k]=Able[i-1][j][k]; return Able[n][2][M]; } ``` 第二版本 時間複雜度$O(n*lg(n))$ ```cpp= funct2(S[],2,M){ left=1,right=n sum=0 sort(S[]) while left<=n and S[left]<=M/2 sum=S[left]+S[right] if sum==M return true else if sum>M right-=1 else left+=1 return false } ``` (b) **初始值:** $Able_{i00}=true、Able_{i1(S_i)}=true、Able_{0jk}=false$ **遞迴式:** $Able_{ijk}=\begin{cases} Able_{(i-1)(j-1)(k-S_i)} \ or \ Able_{(i-1)jk} \ if \ k>=S_i\\ Able_{(i-1)jk} \ if \ k<S_i\\ \end{cases}$ **解答:** $Able_{n3M}$ ```cpp= input:S[],n,3,M funct(S[],3,M){ Able[n][3][M]={false};//將陣列元素全部設為false //Able[i][j][k]代表從s[]的前i個元素中找j個組成k for i= 1:n Able[i][0][0]=true; Able[i][1][S[i]]=true; for i = 1:n for j = 1:i and j<=3 for k=1:M if(k>=S[i]) Able[i][j][k]=Able[i-1][j-1][k-S[i]] or Able[i-1][j][k]; else Able[i][j][k]=Able[i-1][j][k]; return Able[n][3][M]; } ``` (c) **初始值:** $Able_{i00}=true、Able_{i1(S_i)}=true、Able_{0jm}=false$ **遞迴式:** $Able_{ijm}=\begin{cases} Able_{(i-1)(j-1)(m-S_i)} \ or \ Able_{(i-1)jm} \ if \ m>=S_i\\ Able_{(i-1)jm} \ if \ m<S_i\\ \end{cases}$ **解答:** $Able_{nkM}$ ```cpp= input:S[],n,k,M funct(S[],k,M){ Able[n][k][M]={false};//將陣列元素全部設為false //Able[i][j][m]代表從s[]的前i個元素中找j個組成k for i= 1:n Able[i][0][0]=true; Able[i][1][S[i]]=true; for i = 1:n for j = 1:i and j<=k for m=1:M if(m>=S[i]) Able[i][j][m]=Able[i-1][j-1][m-S[i]] or Able[i-1][j][m]; else Able[i][j][m]=Able[i-1][j][m]; return Able[n][k][M]; } ``` 6. How to merge k sorted lists with total length N efficiently. What is the execution time of your algorithm. Answer: use Merge technique to sort k sorted lists 將k個sorted lists視為k個leaf node,那整個merge的樹高會是log(k)+1,而每次merge需要的時間是log(n),n是lists的長度,而每個lists的長度又小於N,因此整體時間複雜度會是$O(N*log(k))$ ```cpp= input:L[1]...L[k] Merge(L[],R[]){ A[L.length+R.length]={};//創建一個空陣列 i=1;j=1;k=1; while(i<=L.length and j<=R.length) if L[i]<=R[j] A[k]=L[i]; k=k+1; i=i+1; else A[k]=R[j]; k=k+1; j=j+1; while(i<=L.length) A[k]=L[i]; k=k+1; i=i+1; while(j<=R.length) A[k]=R[j]; k=k+1; j=j+1; return A[]; } MergeK(){ total=k; while total !=1 j=1; if total%2==0 for i = 1:total/2 L[j]=Merge(L[j],L[j+1]); j=j+2; total=total/2; else for i=1:(total-1)/2 L[j]=Merge(L[j],L[j+1]); j=j+2; total=(total+1)/2; return L[1]; } ``` 7. The input is a sequence of n integers with many duplications, such that the number of distinct integers in the sequence is $O(logn)$. (a) Design a sorting algorithm to sort such sequences using at most $O(nloglogn)$ comparisons in the worst case. (b) Why is the lower bound of sorting $𝛺(nlogn)$ not satisfied in this case? Answer: (a) 假設這n個數字的最大值不是太大,那我們可以用bucket sort排序它,時間複雜度會是 $O(n)$,而且只進行n次比較,因為只需要找出這序列的最大值。 如果最大值很大,或離均差很大,那我們可以用AVL tree去排序它,只要進行n次AVL tree的插入,我們知道每次AVL tree的insert time都是O(log(m)),這同時也是進行比較的次數,這邊的m是AVL tree的節點數量,在此序列當中只有log(n)個不同的數字,所以AVL tree的節點數量就會是log(n),每次插入時間都是$O(log(log(n)))$,而總共進行n次插入,所以總共的比較次數是$O(nlog(log(n)))$。 ```cpp= input:Num[],n; BucketSort(Num[],n){ M=max(Num[]);//找到Num[]當中的最大值 A[M]={0};//創建一個空陣列 for i = 1:n A[Num[i]]=A[Num[i]]+1; return A[]; } AVLsort(Num[],n){ AVLTree={};//建立一個空的AVL tree for i = 1:n AVLTree.insert(Num[i]); return AVLTree } ``` (b) 正常的comparison sort的過程可以比擬為一顆二元樹,假設有n個不同的樹要排序,這棵樹的樹高會是log(n),也是每個數要進行comparison的次數,總共有n個不同數字,所以總共的比較次數會是$O(n*log(n))$,但在這題當中不同的數字不是n個,而是log(n)個,所以不需要進行log(n)次比較,只需要log(log(n))次,對於n個數字來說,總共就會是 $O(log(log(n)))$。