790 likes | 879 Views
親愛的老師您好. 感謝您選用本書作為授課教材,博碩文化準備本書精選簡報檔,特別摘錄重點提供給您授課專用。 說明: 1 、本教具為非賣品,不得作為商業之用。 2 、本教具僅授權使用原著作為授課教材之教師作為教學或研究等學術用途。 3 、本教具未授權提供學生任何拷貝、影印、引用、翻印等行為。 4 、教師若需申請網站或內容授權,可透過您的博碩業務協助處理,謝謝。. 博碩文化: 總公司:台北縣汐止市新台五路一段 94 號 6 樓 A 棟 電話: (02) 2696-2869 分機 313 傳真: (02) 2696-2867
E N D
親愛的老師您好 感謝您選用本書作為授課教材,博碩文化準備本書精選簡報檔,特別摘錄重點提供給您授課專用。 說明: 1、本教具為非賣品,不得作為商業之用。 2、本教具僅授權使用原著作為授課教材之教師作為教學或研究等學術用途。 3、本教具未授權提供學生任何拷貝、影印、引用、翻印等行為。 4、教師若需申請網站或內容授權,可透過您的博碩業務協助處理,謝謝。 博碩文化: 總公司:台北縣汐止市新台五路一段94號6樓A棟 電話:(02) 2696-2869 分機 313 傳真:(02) 2696-2867 網址:www.drmaster.com.tw客服信箱:school@drmaster.com.tw 出書提案信箱 schoolbook@drmaster.com.tw
資料結構 請老師填入姓名主講 課本:圖解資料結構 博碩文化出版發行
第七章 排序 課前指引 「排序」(Sorting)是指將一群資料,按特定規則調換位置,使資料具有某種次序關係(遞增或遞減),例如資料庫內可針對某一欄位進行排序,而此欄位稱為「鍵(key)」,欄位裡面的值我們稱之為「鍵值(key value)」 。
章節大綱 7-1 排序簡介 7-2 內部排序法 7-3 外部排序法 備註:可依進度點選小節
7-1 排序簡介 在排序的過程中,資料的移動方式可分為「直接移動」及「邏輯移動」兩種。 「直接移動」是直接交換儲存資料的位置 「邏輯移動」並不會移動資料儲存位置,僅改變指向這些資料的輔助指標的值。 直接移動排序 邏輯移動排序
7-1 排序簡介 排序的分類 就排序法的選擇來說,當資料量相當大時,排序演算法所花費的時間就顯得相當重要;一個排序法是否為一種有效率(Efficiency)的排序法,取決於其時間複雜度,而時間複雜度的決定因素則是排序過程中資料的交換次數及比較次數的多寡。 這種排序的時間複雜度就是最壞情況 排序前:21 34 45 56 77 81 排序後:81 77 56 45 34 21
7-1 排序簡介 時間複雜度 最好情況(Best Case) 一次遞增排序所使用的時間複雜度就是資料已完成排序,例如原本資料已經完成遞增排序了,如果再進行最好情況。 最壞情況(Worst Case) 指每一鍵值均須重新排列,簡單的例子如原本為遞增排序重新排序成為遞減,就是最壞情況。 平均情況(Average Case) 空間複雜度就是指演算法在執行過程所需付出的額外記憶體空間,排序法所使用到的額外空間愈少,它的空間複雜度就愈佳。
7-2 內部排序法 穩定的排序 指資料在經過排序後,兩個相同鍵值的記錄仍然保持原來的次序,如下例中30左的原始位置在30右的左邊(所謂30左及30右是指相同鍵值一個在左一個在右),穩定的排序(Stable Sort)後7左仍應在7右的左邊,不穩定排序則有可能30左會跑到30右的右邊去: 原始資料順序: 30左 10 65 30右 21 穩定的排序: 10 21 30左30右 65 不穩定的排序: 10 21 30右 30左 65
7-2 內部排序法 氣泡排序法 又稱為交換排序法,是由觀察水中氣泡變化構思而成,原理是由第一個元素開始,比較相鄰元素大小,若大小順序有誤,則對調後再進行下一個元素的比較,就彷彿氣泡逐漸由水底逐漸冒升到水面上一樣。 以下排序利用 55、23、87、62、16數列的排序過程,可以清楚知道氣泡排序法的演算流程: 由小到大排序:
7-2 內部排序法 第一次掃瞄會先拿第一個元素55和第二個元素23作比較,如果第二個元素小於第一個元素,則作交換的動作。 接著拿55和87作比較,就這樣一直比較並交換,到第4次比較完後即可確定最大值在陣列的最後面。
7-2 內部排序法 第二次掃瞄亦從頭比較起,但因最後一個元素在第一次掃瞄就已確定是陣列最大值,故只需比較3次即可把剩餘陣列元素的最大值排到剩餘陣列的最後面。
7-2 內部排序法 第三次掃瞄完,完成三個值的排序 第四次掃瞄完,即可完成所有排序 由此可知5個元素的氣泡排序法必須執行5-1次掃瞄,第一次掃瞄需比較5-1次,共比較4+3+2+1=10次
7-2 內部排序法 範例 7.2.1 數列(43,35,12,9,3,99)經由氣泡排序法(Bubble Sort)由小到大排序,在執行時前三次(Swap)的結果各為何? 解答 第一次交換的結果為(35,43,12,9,3,99) 第二次交換的結果為(35,12,43,9,3,99) 第三次交換的結果為(35,12,9,43,3,99)
7-2 內部排序法 氣泡排序法的C演算法 for (i=n;i>0;i--) /* 掃瞄次數,比較n個值 */ { for (j=0;j<i;j++)/*比較、交換次數*/ { if (data[j]>data[j+1])/* 比較相鄰兩數,如第一數較大則交換 */ { tmp=data[j]; data[j]=data[j+1]; data[j+1]=tmp; /* 交換兩數的過程 */ } } }
7-2 內部排序法 由以上演算法可知,n個元素的氣泡排序法必須執行n-1次掃瞄,最壞清況及平均情況均需比較:(n-1)+(n-2)+(n-3)+…+3+2+1= 次,時 間複雜度為O(n2),最好情況只需完成一次掃瞄,發現沒有做交換的動作則表示已經排序完成,所以只做了n-1次比較,時間複雜度為O(n)。此排序法適用於資料量小或有部份資料已經過排序,而且過程中為相鄰兩者相互比較對調,並不會更改其原本排列的順序,所以是穩定排序法。
7-2 內部排序法 範例 7.2.2 請設計一C程式,並使用氣泡排序法來將以下的數列排序: #include <stdio.h> #include <stdlib.h> int main() { int i,j,tmp; int data[8]={16,25,39,27,12,8,45,63}; /* 原始資料 */ printf("氣泡排序法:\n原始資料為:"); for (i=0;i<8;i++) printf("%3d",data[i]); printf("\n"); for (i=8;i>0;i--) /* 掃瞄次數 */ { for (j=0;j<i;j++)/*比較、交換次數*/ { if (data[j]>data[j+1])/* 比較相鄰兩數,如第一數較大則交換 */ { tmp=data[j]; data[j]=data[j+1]; data[j+1]=tmp; } } printf("第 %d 次排序後的結果是:",6-i); /*把各次掃描後的結果印出*/ for (j=0;j<8;j++) printf("%3d",data[j]); printf("\n"); } printf("排序後結果為:"); for (i=0;i<8;i++) printf("%3d",data[i]); printf("\n"); system("pause"); return 0; } 16,25,39,27,12,8,45,63
7-2 內部排序法 選擇排序法 以下利用55、23、87、62、16數列的由小到大排序過程,來說明選擇排序法的演算流程: 1.首先找到此數列中最小值後與第一個元素交換: 2.從第二個值找起,找到此數列中(不包含第一個)的最小值,再和第二個值交換:
7-2 內部排序法 3.從第三個值找起,找到此數列中(不包含第一、二個)的最小值,再和第三個值交換: 4.從第四個值找起,找到此數列中(不包含第一、二、三個)的最小值,再和第四個值交換,則此排序完成:
7-2 內部排序法 選擇排序法的C演算法 void select (int data[]) { int i,j,tmp,k; for(i=0;i<n;i++) /*掃描n次*/ { for(j=i+1;j<n+1;j++) /*由i+1比較起,比較n次*/ { if(data[i]>data[j]) /*比較第i及第j個元素*/ { tmp=data[i]; data[i]=data[j]; data[j]=tmp; } } } printf("\n"); }
7-2 內部排序法 由以上演算法可知,無論是最壞清況、最佳情況及平均情況都需要找到最大值(或最小值),因此其比較次數為:(n-1)+(n-2)+(n-3)+…+3+2+1= 次;時間複雜度為O(n2)。 此外,由於選擇排序是以最大或最小值直接與最前方未排序的鍵值交換,資料排列順序很有可能被改變,故不是穩定排序,較適用於資料量小或有部份資料已經過排序。
7-2 內部排序法 範例 7.2.3 請設計一C程式,並使用選擇排序法來將以下的數列排序: #include <stdio.h> #include <stdlib.h> void select (int *); /*宣告排序法副程序*/ void showdata (int *); /*宣告列印陣列副程序*/ int main() { int data[8]={16,25,39,27,12,8,45,63}; printf("原始資料為:"); int i; for (i=0;i<8;i++) printf("%3d",data[i]); printf("\n-------------------------------------"); select (data); printf("排序後資料:"); for (i=0;i<8;i++) printf("%3d",data[i]); printf("\n"); system("pause"); return 0; } void showdata (int data[]) { int i; for (i=0;i<8;i++) 16,25,39,27,12,8,45,63
7-2 內部排序法 printf("%3d",data[i]); printf("\n"); } void select (int data[]) { int i,j,tmp,k; for(i=0;i<7;i++) /*掃描5次*/ { for(j=i+1;j<8;j++) /*由i+1比較起,比較5次*/ { if(data[i]>data[j]) /*比較第i及第j個元素*/ { tmp=data[i]; data[i]=data[j]; data[j]=tmp; } } } printf("\n"); }
7-2 內部排序法 插入排序法 是將陣列中的元素,逐一與已排序好的資料作比較,如前兩個元素先排好,再將第三個元素插入適當的位置,所以這三個元素仍然是已排序好,接著再將第四個元素加入,重覆此步驟,直到排序完成為止。 可以看做是在一串有序的記錄R1、R2…Ri,插入新的記錄R,使得i+1個記錄排序妥當。
7-2 內部排序法 以下我們利用55、23、87、62、16數列的由小到大排序過程,來說明插入排序法的演算流程。 下圖中,在步驟二,以23為基準與其他元素比較後,放到適當位置(55的前面),步驟三則拿87與其他兩個元素比較,接著62在比較完前三個數後插入87的前面…將最後一個元素比較完後即完成排序:
7-2 內部排序法 插入排序法的C演算法 void inser(int data[]) { int i; /*i為掃描次數*/ int j; /*以j來定位比較的元素*/ int tmp; /*tmp用來暫存資料*/ for (i=1;i<SIZE;i++) /*掃描迴圈次數為SIZE-1*/ { tmp=data[i]; j=i-1; while (j>=0 && tmp<data[j]) /*如果第二元素小於第一元素*/ { data[j+1]=data[j]; /*就把所有元素往後推一個位置*/ j--; } data[j+1]=tmp; /*最小的元素放到第一個元素*/ } }
7-2 內部排序法 範例 7.2.4 請設計一C程式,並使用選擇排序法來將以下的數列排序: #include <stdio.h> #include <stdlib.h> #define SIZE 8 /*定義陣列大小*/ void inser (int *); /*宣告插入排序法副程式*/ void showdata (int *); /*宣告列印陣列副程式*/ int main() { int data[SIZE]={16,25,39,27,12,8,45,63}; printf("原始陣列是:"); showdata(data); printf("\n"); inser(data); printf("排序後陣列是:"); showdata(data); system("pause"); return 0; } void showdata(int data[]) { int i; for (i=0;i<SIZE;i++) printf("%3d",data[i]); /*列印陣列資料*/ printf("\n"); } 16,25,39,27,12,8,45,63
7-2 內部排序法 void inser(int data[]) { int i; /*i為掃描次數*/ int no; /*以j來定位比較的元素*/ int tmp; /*tmp用來暫存資料*/ for (i=1;i<SIZE;i++) /*掃描迴圈次數為SIZE-1*/ { tmp=data[i]; no=i-1; while (no>=0 && tmp<data[no]) /*如果第二元素小於第一元素*/ { data[no+1]=data[no]; /*就把所有元素往後推一個位置*/ no--; } data[no+1]=tmp; /*最小的元素放到第一個元素*/ } }
7-2 內部排序法 謝耳排序法 是D. L. Shell 在1959年7月所發明的一種排序法,可以減少插入排序法中資料搬移的次數,以加速排序進行。 排序的原則是將資料區分成特定間隔的幾個小區塊,以插入排序法排完區塊內的資料後再漸漸減少間隔的距離。 以下我們仍然利用63、92、27、36、45、71、58、7數列的由小到大排序過程,來說明插入排序法的演算流程:
7-2 內部排序法 首先將所有資料分成Y:(8div2)即Y=4,稱為劃分數。請注意!劃分數不一定要是2,質數是最好。但為演算法方便,所以我們習慣選2。則一開始的間隔設定為 8/2區隔成: 如此一來可得到四個區塊分別是:(6.4)(9.7)(2.5)(3.1)再各別用插入排序法排序成為:(4.6)(7.9)(2.5)(1.3) 接著再縮小間隔為(8/2)/2成
7-2 內部排序法 (4.2.6.5)(7.1.9.3)分別用插入排序法後得到: 最後再以((8/2)/2)/2的間距做插入排序,也就是每一個元素進行排序得到最後的結果: 謝耳排序法的C演算法 void shell(int data[],int size) { int i; /*i為掃描次數*/ int j; /*以j來定位比較的元素*/ int k=1; /*k列印計數*/ int tmp; /*tmp用來暫存資料*/ int jmp; /*設定間距位移量*/ jmp=size/2;
7-2 內部排序法 while (jmp != 0) { for (i=jmp ;i<size ;i++) { tmp=data[i]; j=i-jmp; while(tmp<data[j] && j>=0) /*插入排序法*/ { data[j+jmp] = data[j]; j=j-jmp; } data[jmp+j]=tmp; } jmp=jmp/2; /*控制迴圈數*/ } }
7-2 內部排序法 範例 7.2.5 請設計一C程式,並使用謝耳排序法來將以下的數列排序: #include <stdio.h> #include <stdlib.h> #define SIZE 8 void shell (int *,int); /*宣告排序法副程式*/ void showdata (int *); /*宣告列印陣列副程式*/ int main(void) { int data[SIZE]={16,25,39,27,12,8,45,63}; printf("原始陣列是: "); showdata (data); printf("-----------------------------------------\n"); shell(data,SIZE); system("pause"); return 0; } void showdata(int data[]) { int i; for (i=0;i<SIZE;i++) printf("%3d",data[i]); printf("\n"); } void shell(int data[],int size) { 16,25,39,27,12,8,45,63
7-2 內部排序法 int i; /*i為掃描次數*/ int j; /*以j來定位比較的元素*/ int k=1; /*k列印計數*/ int tmp; /*tmp用來暫存資料*/ int jmp; /*設定間距位移量*/ jmp=size/2; while (jmp != 0) { for (i=jmp ;i<size ;i++) { tmp=data[i]; j=i-jmp; while(tmp<data[j] && j>=0) /*插入排序法*/ { data[j+jmp] = data[j]; j=j-jmp; } data[jmp+j]=tmp; } printf("第 %d 次排序過程:",k++); showdata (data); printf("-----------------------------------------\n"); jmp=jmp/2; /*控制迴圈數*/ } }
7-2 內部排序法 合併排序法 通常是外部儲存裝置最常用的排序方法,工作原理乃是針對已排序好的二個或二個以上的檔案,經由合併的方式,將其組合成一個大的且已排序好的檔案。步驟如下: 1.將N個長度為1的鍵值成對地合併成N/2個長度為2的鍵值組。 2.將N/2個長度為2的鍵值組成對地合併成N/4個長度為4的鍵值組。 3.將鍵值組不斷地合併,直到合併成一組長度為N的鍵值組為止。
7-2 內部排序法 合併排序法的基本演算流程 以條列的方式將步驟整理如下: 1.將N個長度為1的檔案合併成N/2個已排序妥當且長度為2的檔案。 2.將N/2個長度為2的檔案合併成N/4個已排序妥當且長度為4的檔案。 3.將N/4個長度為4的檔案合併成N/8個已排序妥當且長度為8的檔案。 4.將N/2i-1個長度為2i-1的檔案合併成N/2i個已排序妥當且長度為2i的檔案。
7-2 內部排序法 快速排序法 又稱分割交換排序法,是目前公認最佳的排序法,也是使用切割征服(Divide and Conquer)的方式,會先在資料中找到一個隨機會自行設定一個虛擬中間值,並依此中間值將所有打算排序的資料分為兩部份。 假設有n筆R1、R2、R3…Rn記錄,其鍵值為k1、k2、k3…kn: ①先假設K的值為第一個鍵值。 ②由左向右找出鍵值Ki,使得Ki>K。 ③由右向左找出鍵值Kj使得Kj<K。 ④如果i<j,那麼Ki與Kj互換,並回到步驟②。 ⑤若i≧j則將K與Kj交換,並以j為基準點分割成左右部份。然後再針對左右兩邊進行步驟①至⑤,直到左半邊鍵值=右半邊鍵值為止。
7-2 內部排序法 快速排序法將下列資料的排序過程 因為i<j故交換Ki與Kj,然後繼續比較: 因為i<j故交換Ki與Kj,然後繼續比較:
7-2 內部排序法 因為ij故交換K與Kj,並以j為基準點分割成左右兩半: 由上述這幾個步驟,各位可以將小於鍵值K放在左半部;大於鍵值K放在右半部,依上述的排序過程,針對左右兩部份分別排序。過程如下:
7-2 內部排序法 快速排序法的C演算法 void quick(int d[],int size,int lf,int rg) { int i,j,tmp; int lf_idx; int rg_idx; int t; /*1:第一筆鍵值為d[lf]*/ if(lf<rg) { lf_idx=lf+1; rg_idx=rg; step2: printf("[處理過程%d]=> ",process++); for(t=0;t<size;t++) printf("[%2d] ",d[t]); printf("\n"); for(i=lf+1;i<=rg;i++) /*2:由左向右找出一個鍵值大於d[lf]者*/ { if(d[i]>=d[lf]) { lf_idx=i; break; }
7-2 內部排序法 lf_idx++; } for(j=rg;j>=lf+1;j--) /*3:由右向左找出一個鍵值小於d[lf]者*/ { if(d[j]<=d[lf]) { rg_idx=j; break; } rg_idx--; } if(lf_idx<rg_idx) /*4-1:若lf_idx<rg_idx*/ { /*則d[lf_idx]和d[rg_idx]互換*/ tmp = d[lf_idx]; d[lf_idx] = d[rg_idx]; d[rg_idx] = tmp; goto step2; /*4-2:並繼續執行步驟2*/ } if(lf_idx>=rg_idx) /*5-1:若lf_idx大於等於rg_idx*/ { /*則將d[lf]和d[rg_idx]互換*/ tmp = d[lf]; d[lf] = d[rg_idx]; d[rg_idx] = tmp; /*5-2:並以rg_idx為基準點分成左右兩半*/ quick(d,size,lf,rg_idx-1); /*以遞迴方式分別為左 右兩半進行排序*/ quick(d,size,rg_idx+1,rg); /*直至完成排序*/ } } }
7-2 內部排序法 範例 7.2.6 請設計一C程式,並使用快速排序法將數字排序。 #include <stdio.h> #include <stdlib.h> #include <time.h> void inputarr(int*,int); void showdata(int*,int); void quick(int*,int,int,int); int process = 0; int main(void) { int size,data[100]={0}; srand((unsigned)time(NULL)); printf("請輸入陣列大小(100以下):"); scanf("%d",&size); printf("您輸入的原始資料是:"); inputarr (data,size); showdata (data,size); quick(data,size,0,9); printf("\n排序結果:"); showdata(data,size); system("pause"); return 0; } void inputarr(int data[],int size) { int i;
7-2 內部排序法 for (i=0;i<size;i++) data[i]=(rand()%99)+1; } void showdata(int data[],int size) { int i; for (i=0;i<size;i++) printf("%3d",data[i]); printf("\n"); } void quick(int d[],int size,int lf,int rg) { int i,j,tmp; int lf_idx; int rg_idx; int t; /*1:第一筆鍵值為d[lf]*/ if(lf<rg) { lf_idx=lf+1; rg_idx=rg; step2: printf("[處理過程%d]=> ",process++); for(t=0;t<size;t++) printf("[%2d] ",d[t]);
7-2 內部排序法 printf("\n"); for(i=lf+1;i<=rg;i++) /*2:由左向右找出一個鍵值大於d[lf]者*/ { if(d[i]>=d[lf]) { lf_idx=i; break; } lf_idx++; } for(j=rg;j>=lf+1;j--) /*3:由右向左找出一個鍵值小於d[lf]者*/ { if(d[j]<=d[lf]) { rg_idx=j; break; } rg_idx--; } if(lf_idx<rg_idx) /*4-1:若lf_idx<rg_idx*/ { /*則d[lf_idx]和d[rg_idx]互換*/ tmp = d[lf_idx]; d[lf_idx] = d[rg_idx]; d[rg_idx] = tmp;
7-2 內部排序法 goto step2; /*4-2:並繼續執行步驟2*/ } if(lf_idx>=rg_idx) /*5-1:若lf_idx大於等於rg_idx*/ { /*則將d[lf]和d[rg_idx]互換*/ tmp = d[lf]; d[lf] = d[rg_idx]; d[rg_idx] = tmp; /*5-2:並以rg_idx為基準點分成左右兩半*/ quick(d,size,lf,rg_idx-1); /*以遞迴方式分別為左右兩半進行排序*/ quick(d,size,rg_idx+1,rg); /*直至完成排序*/ } } }
7-2 內部排序法 堆積排序法 堆積排序法使用到了二元樹的技巧,它是利用堆積樹來完成排序。堆積是一種特殊的二元樹,可分為最大堆積樹及最小堆積樹兩種。而最大堆積樹滿足以下3個條件: 最小堆積樹則具備以下3個條件: 1.它是一個完整二元樹。 2.所有節點的值都大於或等於它左右子節點的值。 3.樹根是堆積樹中最大的。 1.它是一個完整二元樹。 2.所有節點的值都小於或等於它左右子節點的值。 3.樹根是堆積樹中最小的。
7-2 內部排序法 範例 將利用堆積排序法針對34、19、40、14、57、17、4、43的排序過程示範如下 依下圖數字順序建立完整二元樹
7-2 內部排序法 建立堆積樹 將57自樹根移除,重新建立堆積樹 將43自樹根移除,重新建立堆積樹
7-2 內部排序法 將40自樹根移除,重新建立堆積樹 將34自樹根移除,重新建立堆積樹 將19自樹根移除,重新建立堆積樹
7-2 內部排序法 將17自樹根移除,重新建立堆積樹 將14自樹根移除,重新建立堆積樹 最後將4自樹根移除。得到的排序結果為 57、43、40、34、19、17、14、4 堆積排序法的C演算法 void heap(int *data,int size) { int i,j,tmp; for(i=(size/2);i>0;i--) /*建立堆積樹節點*/ ad_heap(data,i,size-1); printf("\n堆積內容:"); for(i=1;i<size;i++) /*原始堆積樹內容*/
7-2 內部排序法 printf("[%2d] ",data[i]); printf("\n"); for(i=size-2;i>0;i--)/*堆積排序*/ { tmp=data[i+1];/*頭尾節點交換*/ data[i+1]=data[1]; data[1]=tmp; ad_heap(data,1,i);/*處理剩餘節點*/ printf("\n處理過程:"); for(j=1;j<size;j++) printf("[%2d] ",data[j]); } } void ad_heap(int *data,int i,int size) { int j,tmp,post; j=2*i; tmp=data[i]; post=0; while(j<=size && post==0) { if(j<size) {