這一章首先以一個插入排序算法開始,以此爲切入點分析算法。書上以僞代碼來介紹算法,但是在這裏我基本上會用c語言實現一遍,並用自己的語言複述一遍算法的思想。
首先附上插入排序的具體實現。
<span style="font-size:18px;"><span style="font-size:18px;">void insert_sort (EleType *arr , int start , int end) {
int i;
int j;
EleType temp;
for (i = start + 1 ; i <= end ; i++) {
temp = arr[i];
j = i - 1;
for (; j >= start ; j--) {
if (arr[j] > temp)
arr[j+1] = arr[j];
else
break;
}
arr[j+1] = temp;
}
}</span>
</span>
重要思路:運用循環不變式的思想,第一次循環開始,我們不必對只含有一個元素的數組進行排序,因爲其本身就是排序好的。接下來是保持,我們首先用temp變量保存arr[i],這個是要插入元素,在已經排好序的數組查找大於temp的元素,並把它向後移動。最後是終止,將i = end帶入循環不變式,我們可以看見,最後一個元素插入到之前已經排好序的數組當中,所以這個算法是正確的。
對插入排序的分析,由於算法的時間主要是集中在循環上面,所以我們集中精力看循環就可以,當出現最好的情況的時候,arr數組已經排好序了,那麼時間複雜度是n,當出現最壞的情況的時候,時間複雜度是n的平方。(這裏就不在贅述書上的過程了,很簡單)。
下面作者設計了一個分治的排序算法——歸併排序算法。先附上C語言的實現。
<span style="font-size:18px;">void merge (EleType *arr , int start , int mid , int end) {
EleType temp[end - start + 1];
int n1 = 0;
int n2 = 0;
int ntemp = 0;
int i;
while ((n1 != (mid - start + 1)) && (n2 != (end - mid))) {
if (arr[start + n1] < arr[mid+1+n2]) {
temp[ntemp] = arr[start+n1];
ntemp++;
n1++;
}else {
temp[ntemp] = arr[mid+1+n2];
ntemp++;
n2++;
}
}
if (n1 == (mid - start + 1)) {
for (i = mid+1+n2 ; i <= end ; i++) {
temp[ntemp] = arr[i];
ntemp++;
}
}else if (n2 == (end - mid)) {
for (i = n1+start ; i <= mid ; i++) {
temp[ntemp] = arr[i];
ntemp++;
}
}
for (i = start ; i <= end ; i++)
arr[i] = temp[i-start];
}
void merge_sort (EleType *arr , int start , int end) {
int mid;
int i;
if ((end - start) > 0) {
mid = (end + start) / 2;
merge_sort (arr , start , mid);
merge_sort (arr , mid + 1 , end);
merge (arr , start , mid , end);
}
/*for (i = start ; i <= end ; i++)
printf ("%d " , arr[i]);
printf ("\n");
printf ("start = %d , end = %d , mid = %d\n" , start , end , mid);
printf ("\n");*/
}
</span>
重要思路:首先設計歸併算法,將兩個已經排好序的子數組合併成一個排好序的數組。書上在設計這個算法的時候使用了哨兵值,但是在這裏我變通了一下,判斷其中一個子數組是否已經空了,如果空了,就跳出循環,直接把另外一個子數組中的剩下的值,轉移到一個數組當中。再使用遞歸算法排序。因爲一個子數組中如果只有一個元素的話,那麼這個子數組其實就是排好序的。利用這個性質作爲遞歸的出口,設計出遞歸算法。
算法分析:這裏書上介紹了遞歸表達式,利用遞歸表達式,使用以後學習的知識(主方法)可以方便的求出,這個遞歸算法的時間複雜度。這裏我們觀察merge這個函數的複雜度爲n。我們在這裏可以看到,歸併算法把整個算法分治爲兩個部分,每個部分可以處理二分之一的規模(這裏規模定義爲數組的大小)
上圖就是遞歸表達式。
我們如果使用書上畫圖的方法可以求出這個表達式的時間複雜度爲n*lgn。