親愛的老師您好

79
親親親親親親親 親親親親親親親親親親親親親 親親親親親親親親親親親親親親 親親親親親親親親親親親親親親親 ,,。 親親1 親親親親親親親親 親親親親親親親親親 、,。 2 親親親親親親親親親親親親親親親親親親親親親親親親親親親親親親親親 、。 3 親親親親親親親親親親親親親親親 親親親 親親親 親親親親親親 、、、、。 4 親親親親親親親親親親親親親親 親親親親親親親親親親親親親親 親親親 、,,。 親親親親親親親 親親親親親親親親親親親 親 :一 94 親 6 親 A 親 親親(02) 2696-2869 親親 313 親親(02) 2696-2867 親親www.drmaster.com.tw 親親親親[email protected] 親親親親親親 [email protected]

Upload: cain-saunders

Post on 02-Jan-2016

29 views

Category:

Documents


0 download

DESCRIPTION

親愛的老師您好. 感謝您選用本書作為授課教材,博碩文化準備本書精選簡報檔,特別摘錄重點提供給您授課專用。 說明: 1 、本教具為非賣品,不得作為商業之用。 2 、本教具僅授權使用原著作為授課教材之教師作為教學或研究等學術用途。 3 、本教具未授權提供學生任何拷貝、影印、引用、翻印等行為。 4 、教師若需申請網站或內容授權,可透過您的博碩業務協助處理,謝謝。. 博碩文化: 總公司:台北縣汐止市新台五路一段 94 號 6 樓 A 棟 電話: (02) 2696-2869 分機 313 傳真: (02) 2696-2867 - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 親愛的老師您好

親愛的老師您好感謝您選用本書作為授課教材,博碩文化準備本書精選簡報檔,特別摘錄重點提供給您授課專用。說明:1 、本教具為非賣品,不得作為商業之用。2 、本教具僅授權使用原著作為授課教材之教師作為教學或研究等學術用途。3 、本教具未授權提供學生任何拷貝、影印、引用、翻印等行為。4 、教師若需申請網站或內容授權,可透過您的博碩業務協助處理,謝謝。

博碩文化:總公司:台北縣汐止市新台五路一段 94 號 6 樓 A 棟電話: (02) 2696-2869 分機 313 傳真: (02) 2696-2867網址:www.drmaster.com.tw 客服信箱: [email protected]

出書提案信箱 [email protected]

Page 2: 親愛的老師您好

請老師填入姓名主講請老師填入姓名主講

博碩文化出版發行博碩文化出版發行

課本:課本:圖解資料結構圖解資料結構

資料結構資料結構請老師填入姓名主講請老師填入姓名主講

博碩文化出版發行博碩文化出版發行

課本:課本:圖解資料結構圖解資料結構

資料結構資料結構

Page 3: 親愛的老師您好

第七章 第七章 排序 排序

課前指引「排序」( Sorting )是指將一群資料,按特定規則調換位置,使資料具有某種次序關係 ( 遞增或遞減 ) ,例如資料庫內可針對某一欄位進行排序,而此欄位稱為「鍵 (key) 」,欄位裡面的值我們稱之為「鍵值 (key value) 」 。

Page 4: 親愛的老師您好

章節大綱 7-1 排序簡介

7-2 內部排序法

7-3 外部排序法

備註:可依進度點選小節

Page 5: 親愛的老師您好

5

7-1 排序簡介 在排序的過程中,資料的移動方式可分為「直接移動」及「邏輯移動」兩種。「直接移動」是直接交換儲存資料的位置「邏輯移動」並不會移動資料儲存位置,僅改變指向這些資料的輔助指標的值。

直接移動排序 邏輯移動排序

Page 6: 親愛的老師您好

6

7-1 排序簡介 排序的分類

就排序法的選擇來說,當資料量相當大時,排序演算法所花費的時間就顯得相當重要;一個排序法是否為一種有效率 (Efficiency)的排序法,取決於其時間複雜度,而時間複雜度的決定因素則是排序過程中資料的交換次數及比較次數的多寡。

排序前: 21 34 45 56 77 81排序後: 81 77 56 45 34 21

這種排序的時間複雜度就是最壞情況

Page 7: 親愛的老師您好

7

7-1 排序簡介 時間複雜度 最好情況 (Best Case)

一次遞增排序所使用的時間複雜度就是資料已完成排序,例如原本資料已經完成遞增排序了,如果再進行最好情況。

最壞情況 (Worst Case)指每一鍵值均須重新排列,簡單的例子如原本為遞增排序重新排序成為遞減,就是最壞情況。

平均情況 (Average Case)空間複雜度就是指演算法在執行過程所需付出的額外記憶體空間,排序法所使用到的額外空間愈少,它的空間複雜度就愈佳。

Page 8: 親愛的老師您好

8

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

Page 9: 親愛的老師您好

9

7-2 內部排序法 氣泡排序法 又稱為交換排序法,是由觀察水中氣泡變化構思而成,原理是由第一個元素開始,比較相鄰元素大小,若大小順序有誤,則對調後再進行下一個元素的比較,就彷彿氣泡逐漸由水底逐漸冒升到水面上一樣。 以下排序利用 55、 23、 87、 62 、 16數列的排序過程,可以清楚知道氣泡排序法的演算流程: 由小到大排序:

Page 10: 親愛的老師您好

10

7-2 內部排序法

第一次掃瞄會先拿第一個元素 55 和第二個元素 23作比較,如果第二個元素小於第一個元素,則作交換的動作。接著拿 55和 87作比較,就這樣一直比較並交換,到第 4次比較完後即可確定最大值在陣列的最後面。

Page 11: 親愛的老師您好

11

7-2 內部排序法

第二次掃瞄亦從頭比較起,但因最後一個元素在第一次掃瞄就已確定是陣列最大值,故只需比較 3次即可把剩餘陣列元素的最大值排到剩餘陣列的最後面。

Page 12: 親愛的老師您好

12

7-2 內部排序法

第三次掃瞄完,完成三個值的排序

第四次掃瞄完,即可完成所有排序由此可知 5 個元素的氣泡排序法必須執行 5-1次掃瞄,第一次掃瞄需比較 5-1 次,共比較 4+3+2+1=10 次

Page 13: 親愛的老師您好

13

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)

Page 14: 親愛的老師您好

14

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; /* 交換兩數的過程 */

}}}

Page 15: 親愛的老師您好

15

7-2 內部排序法 由以上演算法可知, n 個元素的氣泡排序法必須執行 n-1次掃瞄,最壞清況及平均情況均需比較: (n-1)+(n-2)+(n-3)+…+3+2+1= 次,時

間複雜度為 O(n2),最好情況只需完成一次掃瞄,發現沒有做交換的動作則表示已經排序完成,所以只做了 n-1次比較,時間複雜度為O(n)。此排序法適用於資料量小或有部份資料已經過排序,而且過程中為相鄰兩者相互比較對調,並不會更改其原本排列的順序,所以是穩定排序法。

Page 16: 親愛的老師您好

16

7-2 內部排序法 範例 7.2.2

請設計一 C 程式,並使用氣泡排序法來將以下的數列排序:

16,25,39,27,12,8,45,63

#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;

}

#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;

}

Page 17: 親愛的老師您好

17

7-2 內部排序法 選擇排序法 以下利用 55、 23、 87、 62 、 16數列的由小到大排序過程,來說明選擇排序法的演算流程:

1.首先找到此數列中最小值後與第一個元素交換:

2.從第二個值找起,找到此數列中 (不包含第一個 )的最小值,再和第二個值交換:

Page 18: 親愛的老師您好

18

7-2 內部排序法 3.從第三個值找起,找到此數列中 (不包含第一、二個 )的最小值,再和第三個值交換:

4.從第四個值找起,找到此數列中 (不包含第一、二、三個 )的最小值,再和第四個值交換,則此排序完成:

Page 19: 親愛的老師您好

19

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");

}

Page 20: 親愛的老師您好

20

7-2 內部排序法 由以上演算法可知,無論是最壞清況、最佳情況及平均情況都需要找到最大值 (或最小值 ),因此其比較次數為: (n-1)+(n-2)+(n-3)+…+3+2+1=

次;時間複雜度為 O(n2)。此外,由於選擇排序是以最大或最小值直接與最前方未排序的鍵值交換,資料排列順序很有可能被改變,故不是穩定排序,較適用於資料量小或有部份資料已經過排序。

Page 21: 親愛的老師您好

21

7-2 內部排序法 範例 7.2.3

請設計一 C 程式,並使用選擇排序法來將以下的數列排序: 16,25,39,27,12,8,45,63

#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++)

#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++)

Page 22: 親愛的老師您好

22

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");

}

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");

}

Page 23: 親愛的老師您好

23

7-2 內部排序法 插入排序法

是將陣列中的元素,逐一與已排序好的資料作比較,如前兩個元素先排好,再將第三個元素插入適當的位置,所以這三個元素仍然是已排序好,接著再將第四個元素加入,重覆此步驟,直到排序完成為止。可以看做是在一串有序的記錄 R1 、 R2…Ri,插入新的記錄 R ,使得 i+1 個記錄排序妥當。

Page 24: 親愛的老師您好

24

7-2 內部排序法 以下我們利用 55、 23、 87、 62 、 16數列的由小到大排序過程,來說明插入排序法的演算流程。下圖中,在步驟二,以 23 為基準與其他元素比較後,放到適當位置 (55的前面 ),步驟三則拿 87與其他兩個元素比較,接著 62 在比較完前三個數後插入 87的前面…將最後一個元素比較完後即完成排序:

Page 25: 親愛的老師您好

25

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; /* 最小的元素放到第一個元素 */

}}

Page 26: 親愛的老師您好

26

7-2 內部排序法 範例 7.2.4

請設計一 C 程式,並使用選擇排序法來將以下的數列排序: 16,25,39,27,12,8,45,63

#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");

}

#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");

}

Page 27: 親愛的老師您好

27

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; /* 最小的元素放到第一個元素 */}

}

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; /* 最小的元素放到第一個元素 */}

}

Page 28: 親愛的老師您好

28

7-2 內部排序法 謝耳排序法

是 D. L. Shell 在 1959年 7月所發明的一種排序法,可以減少插入排序法中資料搬移的次數,以加速排序進行。排序的原則是將資料區分成特定間隔的幾個小區塊,以插入排序法排完區塊內的資料後再漸漸減少間隔的距離。以下我們仍然利用 63 、 92 、 27、 36、 45、71 、 58、 7數列的由小到大排序過程,來說明插入排序法的演算流程:

Page 29: 親愛的老師您好

29

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 成

Page 30: 親愛的老師您好

30

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;

Page 31: 親愛的老師您好

31

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; /*控制迴圈數 */

}}

Page 32: 親愛的老師您好

32

7-2 內部排序法 範例 7.2.5

請設計一 C 程式,並使用謝耳排序法來將以下的數列排序: 16,25,39,27,12,8,45,63

#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){

#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){

Page 33: 親愛的老師您好

33

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; /* 控制迴圈數 */

}}

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; /* 控制迴圈數 */

}}

Page 34: 親愛的老師您好

34

7-2 內部排序法 合併排序法 通常是外部儲存裝置最常用的排序方法,工作原理乃是針對已排序好的二個或二個以上的檔案,經由合併的方式,將其組合成一個大的且已排序好的檔案。步驟如下: 1. 將 N 個長度為 1 的鍵值成對地合併成 N/2 個長度為 2的鍵值組。2. 將 N/2 個長度為 2 的鍵值組成對地合併成 N/4 個長度為 4 的鍵值組。3. 將鍵值組不斷地合併,直到合併成一組長度為 N 的鍵值組為止。

Page 35: 親愛的老師您好

35

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 的檔案。

Page 36: 親愛的老師您好

36

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 為基準點分割成左右部份。然後再針對左右兩邊進行步驟①至⑤,直到左半邊鍵值 =右半邊鍵值為止。

Page 37: 親愛的老師您好

37

7-2 內部排序法 快速排序法將下列資料的排序過程

因為 i<j故交換 Ki 與 Kj,然後繼續比較:

因為 i<j故交換 Ki與 Kj,然後繼續比較:

Page 38: 親愛的老師您好

38

7-2 內部排序法 因為 ij 故交換 K 與 Kj,並以 j 為基準點分割成左右兩半:

由上述這幾個步驟,各位可以將小於鍵值 K 放在左半部;大於鍵值 K 放在右半部,依上述的排序過程,針對左右兩部份分別排序。過程如下:

Page 39: 親愛的老師您好

39

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;

}

Page 40: 親愛的老師您好

40

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); /*直至完成排序 */}

}}

Page 41: 親愛的老師您好

41

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;

#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;

Page 42: 親愛的老師您好

42

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]);

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]);

Page 43: 親愛的老師您好

43

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;

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;

Page 44: 親愛的老師您好

44

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); /* 直至完成排序 */

}}

}

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); /* 直至完成排序 */

}}

}

Page 45: 親愛的老師您好

45

7-2 內部排序法 堆積排序法堆積排序法使用到了二元樹的技巧,它是利用堆積樹來完成排序。堆積是一種特殊的二元樹,可分為最大堆積樹及最小堆積樹兩種。而最大堆積樹滿足以下 3 個條件:

最小堆積樹則具備以下 3 個條件:

1.它是一個完整二元樹。2. 所有節點的值都大於或等於它左右子節點的值。3.樹根是堆積樹中最大的。

1.它是一個完整二元樹。2. 所有節點的值都小於或等於它左右子節點的值。3.樹根是堆積樹中最小的。

Page 46: 親愛的老師您好

46

7-2 內部排序法 範例

將利用堆積排序法針對 34、 19、 40、 14、 57、 17、 4、 43 的排序過程示範如下

依下圖數字順序建立完整二元樹

Page 47: 親愛的老師您好

47

7-2 內部排序法 建立堆積樹

將 57 自樹根移除,重新建立堆積樹

將 43自樹根移除,重新建立堆積樹

Page 48: 親愛的老師您好

48

7-2 內部排序法 將 40 自樹根移除,重新建立堆積樹

將 34自樹根移除,重新建立堆積樹

將 19 自樹根移除,重新建立堆積樹

Page 49: 親愛的老師您好

49

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++) /* 原始堆積樹內容 */

Page 50: 親愛的老師您好

50

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){

Page 51: 親愛的老師您好

51

7-2 內部排序法

if(data[j]<data[j+1])/*找出最大節點 */j++;

}if(tmp>=data[j])/* 若樹根較大,結束比較過程 */

post=1;else{

data[j/2]=data[j];/* 若樹根較小,則繼續比較 */j=2*j;

} } data[j/2]=tmp;/* 指定樹根為父節點 */}

Page 52: 親愛的老師您好

52

7-2 內部排序法 範例 7.2.7

請設計一 C 程式,並使用堆積排序法來排序。

#include <stdio.h>void heap(int*,int);void ad_heap(int*,int,int);int main(void){

int i,size,data[9]={0,5,6,4,8,3,2,7,1}; /* 原始陣列內容 */size=9;printf(" 原始陣列: ");for(i=1;i<size;i++)

printf("[%2d] ",data[i]);heap(data,size);/* 建立堆積樹 */printf("\n 排序結果: ");for(i=1;i<size;i++)

printf("[%2d] ",data[i]);printf("\n");system("pause");return 0;

}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++) /* 原始堆積樹內容 */

#include <stdio.h>void heap(int*,int);void ad_heap(int*,int,int);int main(void){

int i,size,data[9]={0,5,6,4,8,3,2,7,1}; /* 原始陣列內容 */size=9;printf(" 原始陣列: ");for(i=1;i<size;i++)

printf("[%2d] ",data[i]);heap(data,size);/* 建立堆積樹 */printf("\n 排序結果: ");for(i=1;i<size;i++)

printf("[%2d] ",data[i]);printf("\n");system("pause");return 0;

}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++) /* 原始堆積樹內容 */

Page 53: 親愛的老師您好

53

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){

if(data[j]<data[j+1])/* 找出最大節點 */j++;

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){

if(data[j]<data[j+1])/* 找出最大節點 */j++;

Page 54: 親愛的老師您好

54

7-2 內部排序法 }

if(tmp>=data[j])/* 若樹根較大,結束比較過程 */post=1;

else{

data[j/2]=data[j];/* 若樹根較小,則繼續比較 */j=2*j;

} } data[j/2]=tmp;/* 指定樹根為父節點 */}

}if(tmp>=data[j])/* 若樹根較大,結束比較過程 */

post=1;else{

data[j/2]=data[j];/* 若樹根較小,則繼續比較 */j=2*j;

} } data[j/2]=tmp;/* 指定樹根為父節點 */}

Page 55: 親愛的老師您好

55

7-2 內部排序法 基數排序法 基數排序法依比較的方向可分為最有效鍵優先(Most Significant Digit First:MSD)和最無效鍵優先 (Least Significant Digit First:LSD)兩種。底下的範例以 LSD將三位數的整數資料來加以排序,它是依個位數、十位數、百位數來進行排序。以下最無效鍵優先 (LSD)例子的說明,便可清楚的知道它的動作原理:

原始資料:

Page 56: 親愛的老師您好

56

7-2 內部排序法 步驟一:把每個整數依其個位數字放到串列中

合併後成為

步驟二:再依其十位數字,依序放到串列中:

合併後成為

Page 57: 親愛的老師您好

57

7-2 內部排序法 步驟三:再依其百位數字,依序放到串列中

最後合併即完成排序

Page 58: 親愛的老師您好

58

7-2 內部排序法 基數排序法的 C 演算法 void radix(int data[],int size){

int i,j,k,n,m;for (n=1;n<=100;n=n*10)/*n 為基數,由個位數開始排序 */{

int tmp[10][100]={0};/* 設定暫存陣列, [0~9 位數 ][ 資料個數 ] ,所有內容均為 0 */

for (i=0;i<size;i++)/* 比對所有資料 */{

m=(data[i]/n)%10;/* m 為 n 位數的值,如 36 取十位數 (36/10)%10=3 */

tmp[m][i]=data[i];/* 把 data[i] 的值暫存於 tmp 裡 */

}k=0;for (i=0;i<10;i++)

{ for(j=0;j<size;j++){

if(tmp[i][j] != 0) /* 因一開始設定 tmp ={0} ,故不為 0者即為 */

{

Page 59: 親愛的老師您好

59

7-2 內部排序法 data[k]=tmp[i][j];/* data暫存在 tmp 裡的值,把 tmp 裡的值放 */

k++; /* 回 data[ ] 裡 */}

}}printf(" 經過 %3d 位數排序後: ",n);showdata(data,size);

} }

Page 60: 親愛的老師您好

60

7-2 內部排序法 範例 7.2.8

請設計一 C 程式,並使用基數排序法來排序。

/* 基數排序法 由小到大排序 */#include <stdio.h>#include <stdlib.h>#include <time.h>void radix (int *,int);/* 基數排序法副程式 */void showdata (int *,int);void inputarr (int *,int);int main(void){

int size,data[100]={0};printf(" 請輸入陣列大小 (100 以下 ) : ");scanf("%d",&size);printf(" 您輸入的原始資料是: \n");inputarr (data,size);showdata (data,size);radix (data,size);system("pause");return 0;

}void inputarr(int data[],int size){

int i;srand((unsigned)time(NULL));

for (i=0;i<size;i++)data[i]=(rand()%999)+1;/* 設定 data 值最大為 3 位數 */

/* 基數排序法 由小到大排序 */#include <stdio.h>#include <stdlib.h>#include <time.h>void radix (int *,int);/* 基數排序法副程式 */void showdata (int *,int);void inputarr (int *,int);int main(void){

int size,data[100]={0};printf(" 請輸入陣列大小 (100 以下 ) : ");scanf("%d",&size);printf(" 您輸入的原始資料是: \n");inputarr (data,size);showdata (data,size);radix (data,size);system("pause");return 0;

}void inputarr(int data[],int size){

int i;srand((unsigned)time(NULL));

for (i=0;i<size;i++)data[i]=(rand()%999)+1;/* 設定 data 值最大為 3 位數 */

Page 61: 親愛的老師您好

61

7-2 內部排序法 }void showdata(int data[],int size){

int i;for (i=0;i<size;i++)

printf("%5d",data[i]);printf("\n");

}void radix(int data[],int size){

int i,j,k,n,m;for (n=1;n<=100;n=n*10)/*n 為基數,由個位數開始排序 */{ int tmp[10][100]={0};/* 設定暫存陣列, [0~9 位數 ][ 資料個數 ] ,所有內容均為

0 */for (i=0;i<size;i++)/* 比對所有資料 */{ m=(data[i]/n)%10;/* m 為 n 位數的值,如 36 取十位數 (36/10)%10=3

*/tmp[m][i]=data[i];/* 把 data[i] 的值暫存於 tmp 裡 */

}k=0;for (i=0;i<10;i++){

for(j=0;j<size;j++){ if(tmp[i][j] != 0)/* 因一開始設定 tmp ={0} ,故不為 0 者即為 */

}void showdata(int data[],int size){

int i;for (i=0;i<size;i++)

printf("%5d",data[i]);printf("\n");

}void radix(int data[],int size){

int i,j,k,n,m;for (n=1;n<=100;n=n*10)/*n 為基數,由個位數開始排序 */{ int tmp[10][100]={0};/* 設定暫存陣列, [0~9 位數 ][ 資料個數 ] ,所有內容均為

0 */for (i=0;i<size;i++)/* 比對所有資料 */{ m=(data[i]/n)%10;/* m 為 n 位數的值,如 36 取十位數 (36/10)%10=3

*/tmp[m][i]=data[i];/* 把 data[i] 的值暫存於 tmp 裡 */

}k=0;for (i=0;i<10;i++){

for(j=0;j<size;j++){ if(tmp[i][j] != 0)/* 因一開始設定 tmp ={0} ,故不為 0 者即為 */

Page 62: 親愛的老師您好

62

7-2 內部排序法 {

data[k]=tmp[i][j];/* data 暫存在 tmp 裡的值,把 tmp 裡的值放 */

k++; /* 回 data[ ] 裡 */}

}}printf(" 經過 %3d 位數排序後: ",n);showdata(data,size);

}}

{ data[k]=tmp[i][j];/* data 暫存在 tmp 裡的值,把 tmp 裡的值放

*/k++; /* 回 data[ ] 裡 */}

}}printf(" 經過 %3d 位數排序後: ",n);showdata(data,size);

}}

Page 63: 親愛的老師您好

63

7-3 外部排序法 直接合併排序法

直接合併排序法 (Direct Merge Sort)是外部儲存裝置最常用的排序方法。它可以分為兩個步驟:

例如:把一個檔案分成 6個小檔案:

1. 將欲排序的檔案分為幾個可以載入記憶體空間大小的小檔案,再使用內部排序法將各檔案內的資料排序。2. 將第一步驟所建立的小檔案每二個合併成一個檔案。兩兩合併後,把所有檔案合併成一個檔案後就可以完成排序了。

Page 64: 親愛的老師您好

64

7-3 外部排序法 小檔案都完成排序後,兩兩合併成一個較大的檔案,最後再合併成一個檔案即可完成。

合併排序法的 C 演算法 void merge(FILE *fp, FILE *fp1, FILE *fp2){

int n1,n2;/*宣告變數 n1 , n2暫存資料檔 data1 及 data2 內的元素值 */n1=getc(fp1);/* 從 fp1 中捉一個元素進來,存在 n1*/n2=getc(fp2);while(feof(fp1)==0 && feof(fp2)==0)/*判斷是否已到檔尾 */{

if (n1 <= n2){

putc(n1,fp);/* 如果 n1 比較小,則把 n1 存到 fp 裡 */n1=getc(fp1);/* 接著讀下一筆 n1 的資料 */

Page 65: 親愛的老師您好

65

7-3 外部排序法 }else{

putc(n2,fp);/* 如果 n2 比較小,則把 n2 存到 fp 裡 */ n2=getc(fp2);/* 接著讀下一筆 n2 的資料 */

}}if(feof(fp1))/* 如果其中一個資料檔已讀取完畢,經判斷後 */{

putc(n2,fp);/*把另一個資料檔內的資料全部放到 fp 裡 */while (1){

n2=getc(fp2);if(feof(fp2)) break;putc(n2,fp);

}}else if (feof(fp2)){

putc(n1,fp);while(feof(fp1)){

n1=getc(fp1);putc(n1,fp);

}}

}

Page 66: 親愛的老師您好

66

7-3 外部排序法 範例 7.2.9

請設計一 C 程式,並使用合併排序法來將兩個已經排序好的檔案合併與排序成一個檔案: data1.dat : 1 3 4 5 dara2.dat : 2 6 7 9

#include <stdio.h>#include <stdlib.h>void merge(FILE *fp, FILE *fp1, FILE *fp2){

int n1,n2;/* 宣告變數 n1 , n2 暫存資料檔 data1 及 data2 內的元素值 */n1=getc(fp1);/* 從 fp1 中捉一個元素進來,存在 n1*/n2=getc(fp2);while(feof(fp1)==0 && feof(fp2)==0)/* 判斷是否已到檔尾 */{

if (n1 <= n2){

putc(n1,fp);/* 如果 n1 比較小,則把 n1 存到 fp 裡 */n1=getc(fp1);/* 接著讀下一筆 n1 的資料 */

}else{

putc(n2,fp);/* 如果 n2 比較小,則把 n2 存到 fp 裡 */ n2=getc(fp2);/* 接著讀下一筆 n2 的資料 */

}}if(feof(fp1))/* 如果其中一個資料檔已讀取完畢,經判斷後 */{ putc(n2,fp);/* 把另一個資料檔內的資料全部放到 fp 裡 */

while (1){

#include <stdio.h>#include <stdlib.h>void merge(FILE *fp, FILE *fp1, FILE *fp2){

int n1,n2;/* 宣告變數 n1 , n2 暫存資料檔 data1 及 data2 內的元素值 */n1=getc(fp1);/* 從 fp1 中捉一個元素進來,存在 n1*/n2=getc(fp2);while(feof(fp1)==0 && feof(fp2)==0)/* 判斷是否已到檔尾 */{

if (n1 <= n2){

putc(n1,fp);/* 如果 n1 比較小,則把 n1 存到 fp 裡 */n1=getc(fp1);/* 接著讀下一筆 n1 的資料 */

}else{

putc(n2,fp);/* 如果 n2 比較小,則把 n2 存到 fp 裡 */ n2=getc(fp2);/* 接著讀下一筆 n2 的資料 */

}}if(feof(fp1))/* 如果其中一個資料檔已讀取完畢,經判斷後 */{ putc(n2,fp);/* 把另一個資料檔內的資料全部放到 fp 裡 */

while (1){

Page 67: 親愛的老師您好

67

7-3 外部排序法 n2=getc(fp2);if(feof(fp2)) break;putc(n2,fp);

}}else if (feof(fp2)){

putc(n1,fp);while(feof(fp1)){

n1=getc(fp1);putc(n1,fp);

}}

}int main(void){

char n;FILE *fp=fopen("data.txt","w+");/* 宣告、並開啟建立新檔主檔指標 fp*/FILE *fp1=fopen("data1.txt","r");/* 宣告資料檔 1 指標 fp1*/FILE *fp2=fopen("data2.txt","r");/* 宣告資料檔 2 指標 fp2*/FILE *f,*f1,*f2;if(fp==NULL)

printf(" 開啟主檔失敗 \n");else if(fp1==NULL)

n2=getc(fp2);if(feof(fp2)) break;putc(n2,fp);

}}else if (feof(fp2)){

putc(n1,fp);while(feof(fp1)){

n1=getc(fp1);putc(n1,fp);

}}

}int main(void){

char n;FILE *fp=fopen("data.txt","w+");/* 宣告、並開啟建立新檔主檔指標 fp*/FILE *fp1=fopen("data1.txt","r");/* 宣告資料檔 1 指標 fp1*/FILE *fp2=fopen("data2.txt","r");/* 宣告資料檔 2 指標 fp2*/FILE *f,*f1,*f2;if(fp==NULL)

printf(" 開啟主檔失敗 \n");else if(fp1==NULL)

Page 68: 親愛的老師您好

68

7-3 外部排序法 printf(" 開啟資料檔 1 失敗 \n");/* 開啟檔案成功時,指標會傳回 FILE 檔案 */else if(fp2==NULL)/* 指標,開啟失敗則傳回 NULL 值 */

printf(" 開啟資料檔 2 失敗 \n");else{

printf(" 資料排序中 ......\n"); merge(fp,fp1,fp2);

printf(" 資料處理完成 !!\n");}fclose(fp); /* 關閉檔案 */fclose(fp1);fclose(fp2);printf("data1.txt 資料內容為: \n");f1=fopen("data1.txt","r");while(1){

n=getc(f1);if(feof(f1)) break;

printf("[%c] ",n);}printf("\n");printf("data2.txt 資料內容為: \n");f2=fopen("data2.txt","r");while(1){

printf(" 開啟資料檔 1 失敗 \n");/* 開啟檔案成功時,指標會傳回 FILE 檔案 */else if(fp2==NULL)/* 指標,開啟失敗則傳回 NULL 值 */

printf(" 開啟資料檔 2 失敗 \n");else{

printf(" 資料排序中 ......\n"); merge(fp,fp1,fp2);

printf(" 資料處理完成 !!\n");}fclose(fp); /* 關閉檔案 */fclose(fp1);fclose(fp2);printf("data1.txt 資料內容為: \n");f1=fopen("data1.txt","r");while(1){

n=getc(f1);if(feof(f1)) break;

printf("[%c] ",n);}printf("\n");printf("data2.txt 資料內容為: \n");f2=fopen("data2.txt","r");while(1){

Page 69: 親愛的老師您好

69

7-3 外部排序法 n=getc(f2);

if(feof(f2)) break;printf("[%c] ",n);

}printf("\n");printf(" 排序後 data.txt 資料內容為: \n");f=fopen("data.txt","r");while(1){

n=getc(f);if(feof(f)) break;printf("[%c] ",n);

}printf("\n");printf("\n");fclose(f); /* 關閉檔案 */fclose(f1);fclose(f2);system("pause");return 0;

}

n=getc(f2);if(feof(f2)) break;

printf("[%c] ",n);}printf("\n");printf(" 排序後 data.txt 資料內容為: \n");f=fopen("data.txt","r");while(1){

n=getc(f);if(feof(f)) break;printf("[%c] ",n);

}printf("\n");printf("\n");fclose(f); /* 關閉檔案 */fclose(f1);fclose(f2);system("pause");return 0;

}

Page 70: 親愛的老師您好

70

7-3 外部排序法 範例 7.2.10

請設計一 C 程式,利用合併排序法將一檔案拆成兩個或兩個以上的行程 (runs),再利用上一個程式所介紹的方法合併成一個檔案。

#include <stdio.h>#include <stdlib.h>void merge (FILE *,FILE *,FILE *);/* 宣告合併排序副程式 */void me (FILE *,FILE *,FILE *,FILE *);/* 宣告分割檔案副程式 */void bubble(FILE *,int);/* 已分割檔案中,以氣泡排序法進行內部排序 */void showdata(FILE *); /* 印出檔案內容 */int main(void){

char n;FILE *fp=fopen("datafile.txt","r");/* 宣告檔案指標 */FILE *fp1=fopen("sortdata.txt","w+");FILE *ff1=fopen("sort1.txt","w+");FILE *ff2=fopen("sort2.txt","w+");if(fp==NULL)/* 檔案是否開啟成功 */

printf(" 開啟原資料檔失敗 \n");else if(fp1==NULL)

printf(" 開啟合併後檔案失敗 \n");else if(ff1==NULL)

printf(" 開啟分割檔 1 失敗 \n");else if(ff2==NULL)

printf(" 開啟分割案 2 失敗 \n");else{

printf(" 檔案分割中 ......\n"); me(fp,fp1,ff1,ff2);

#include <stdio.h>#include <stdlib.h>void merge (FILE *,FILE *,FILE *);/* 宣告合併排序副程式 */void me (FILE *,FILE *,FILE *,FILE *);/* 宣告分割檔案副程式 */void bubble(FILE *,int);/* 已分割檔案中,以氣泡排序法進行內部排序 */void showdata(FILE *); /* 印出檔案內容 */int main(void){

char n;FILE *fp=fopen("datafile.txt","r");/* 宣告檔案指標 */FILE *fp1=fopen("sortdata.txt","w+");FILE *ff1=fopen("sort1.txt","w+");FILE *ff2=fopen("sort2.txt","w+");if(fp==NULL)/* 檔案是否開啟成功 */

printf(" 開啟原資料檔失敗 \n");else if(fp1==NULL)

printf(" 開啟合併後檔案失敗 \n");else if(ff1==NULL)

printf(" 開啟分割檔 1 失敗 \n");else if(ff2==NULL)

printf(" 開啟分割案 2 失敗 \n");else{

printf(" 檔案分割中 ......\n"); me(fp,fp1,ff1,ff2);

Page 71: 親愛的老師您好

71

7-3 外部排序法 printf(" 檔案排序中 ......\n");

printf(" 資料處理完成 !!\n");}rewind(fp); /* 重設各檔案指標 */rewind(fp1);rewind(ff1);rewind(ff2);

/* 列印檔案內容 */printf(" 原始檔 datafile.txt 資料內容為: \n");showdata(fp);printf("\n 分割檔 sort1.txt 資料內容為: \n");showdata(ff1);printf("\n 分割檔 sort2.txt 資料內容為: \n");showdata(ff2);printf("\n 排序後 sortdata.txt 資料內容為: \n");showdata(fp1);fclose(fp);/* 關閉檔案 */fclose(fp1);fclose(ff1);fclose(ff2);system("pause");return 0;

}void me(FILE *fp,FILE *fp1,FILE *ff1,FILE *ff2){

printf(" 檔案排序中 ......\n");printf(" 資料處理完成 !!\n");

}rewind(fp); /* 重設各檔案指標 */rewind(fp1);rewind(ff1);rewind(ff2);

/* 列印檔案內容 */printf(" 原始檔 datafile.txt 資料內容為: \n");showdata(fp);printf("\n 分割檔 sort1.txt 資料內容為: \n");showdata(ff1);printf("\n 分割檔 sort2.txt 資料內容為: \n");showdata(ff2);printf("\n 排序後 sortdata.txt 資料內容為: \n");showdata(fp1);fclose(fp);/* 關閉檔案 */fclose(fp1);fclose(ff1);fclose(ff2);system("pause");return 0;

}void me(FILE *fp,FILE *fp1,FILE *ff1,FILE *ff2){

Page 72: 親愛的老師您好

72

7-3 外部排序法 int n1=0,n2=0;char da1,da2;while(1){

da1=getc(fp);if(feof(fp)) break;n1++;/* 計數, n1 為 datafile 的總筆數 */

}rewind(fp);for(n2=0;n2<(n1/2);n2++){

da2=getc(fp);/* 把一半的檔案 */ putc(da2,ff1);/* 分割到 ff1 去 */}rewind(ff1);

bubble(ff1,n2);/* 分割完後呼叫 bubble 副程式進行排序 */while(1){

da2=getc(fp);/* 把其他的 datafile 檔案 */ if(feof(fp)) break;

putc(da2,ff2);/* 內容分割到 ff2 去 */}rewind(ff2);bubble(ff2,(n1/2));/* 分割完後呼叫 bubble 副程式進行排序 */rewind(ff1);

int n1=0,n2=0;char da1,da2;while(1){

da1=getc(fp);if(feof(fp)) break;n1++;/* 計數, n1 為 datafile 的總筆數 */

}rewind(fp);for(n2=0;n2<(n1/2);n2++){

da2=getc(fp);/* 把一半的檔案 */ putc(da2,ff1);/* 分割到 ff1 去 */}rewind(ff1);

bubble(ff1,n2);/* 分割完後呼叫 bubble 副程式進行排序 */while(1){

da2=getc(fp);/* 把其他的 datafile 檔案 */ if(feof(fp)) break;

putc(da2,ff2);/* 內容分割到 ff2 去 */}rewind(ff2);bubble(ff2,(n1/2));/* 分割完後呼叫 bubble 副程式進行排序 */rewind(ff1);

Page 73: 親愛的老師您好

73

7-3 外部排序法 rewind(ff2);merge(fp1,ff1,ff2);/* 呼叫合併排序副程式 */

}void merge(FILE *fp, FILE *fp1, FILE *fp2){

char n1,n2; /* 宣告變數 n1 , n2 暫存資料檔 data1 及 data2 內的元素值 */n1=getc(fp1);/* 從 fp1 中捉一個元素進來,存在 n1*/n2=getc(fp2);while(feof(fp1)==0 && feof(fp2)==0) /* 判斷是否已到檔尾 */{

if (n1 <= n2){

putc(n1,fp);/* 如果 n1 比較小,則把 n1 存到 fp 裡 */n1=getc(fp1);/* 接著讀下一筆 n1 的資料 */

}else{

putc(n2,fp);/* 如果 n2 比較小,則把 n2 存到 fp 裡 */n2=getc(fp2);/* 接著讀下一筆 n2 的資料 */

}}if(feof(fp1))/* 如果其中一個資料檔已讀取完畢,經判斷後 */{

putc(n2,fp);/* 把另一個資料檔內的資料全部放到 fp 裡 */while (1)

rewind(ff2);merge(fp1,ff1,ff2);/* 呼叫合併排序副程式 */

}void merge(FILE *fp, FILE *fp1, FILE *fp2){

char n1,n2; /* 宣告變數 n1 , n2 暫存資料檔 data1 及 data2 內的元素值 */n1=getc(fp1);/* 從 fp1 中捉一個元素進來,存在 n1*/n2=getc(fp2);while(feof(fp1)==0 && feof(fp2)==0) /* 判斷是否已到檔尾 */{

if (n1 <= n2){

putc(n1,fp);/* 如果 n1 比較小,則把 n1 存到 fp 裡 */n1=getc(fp1);/* 接著讀下一筆 n1 的資料 */

}else{

putc(n2,fp);/* 如果 n2 比較小,則把 n2 存到 fp 裡 */n2=getc(fp2);/* 接著讀下一筆 n2 的資料 */

}}if(feof(fp1))/* 如果其中一個資料檔已讀取完畢,經判斷後 */{

putc(n2,fp);/* 把另一個資料檔內的資料全部放到 fp 裡 */while (1)

Page 74: 親愛的老師您好

74

7-3 外部排序法 {

n2=getc(fp2);if(feof(fp2)) break;putc(n2,fp);

}}else if (feof(fp2)){

putc(n1,fp);while (feof(fp1)){

n1=getc(fp1);putc(n1,fp);

}}

}void bubble (FILE *ff,int size){

int ii=0,j,i,tmp,flag;char n;char data[100]={0};for(i=0;i<size;i++){

n=getc(ff);if(feof(ff)) break;

data[i]=n;ii++;

{ n2=getc(fp2);if(feof(fp2)) break;putc(n2,fp);

}}else if (feof(fp2)){

putc(n1,fp);while (feof(fp1)){

n1=getc(fp1);putc(n1,fp);

}}

}void bubble (FILE *ff,int size){

int ii=0,j,i,tmp,flag;char n;char data[100]={0};for(i=0;i<size;i++){

n=getc(ff);if(feof(ff)) break;

data[i]=n;ii++;

Page 75: 親愛的老師您好

75

7-3 外部排序法 }

for(i=size;i>0;i--){

flag=0;/*flag 用來判斷是否有執行交換的動作 */for (j=0;j<i;j++){

if(data[j+1]<data[j]){

tmp=data[j];data[j]=data[j+1];data[j+1]=tmp;flag++;

}/* 如果有執行過交換,則 flag 不為 0*/}if(flag==0)break;

}rewind(ff);for(i=1;i<=size;i++)

putc(data[i],ff);}void showdata (FILE *ff){

char n;while(1){

n=getc(ff);if(feof(ff)) break;

printf("[%c]",n);}printf("\n");

}

}for(i=size;i>0;i--){

flag=0;/*flag 用來判斷是否有執行交換的動作 */for (j=0;j<i;j++){

if(data[j+1]<data[j]){

tmp=data[j];data[j]=data[j+1];data[j+1]=tmp;flag++;

}/* 如果有執行過交換,則 flag 不為 0*/}if(flag==0)break;

}rewind(ff);for(i=1;i<=size;i++)

putc(data[i],ff);}void showdata (FILE *ff){

char n;while(1){

n=getc(ff);if(feof(ff)) break;

printf("[%c]",n);}printf("\n");

}

Page 76: 親愛的老師您好

76

7-3 外部排序法 k 路合併法 描述利用 3路合併 (3-way merge)來處理 27個行程 (Runs)的示意圖

使用 k-way合併的原意是希望減少輸出入時間,但合併 k 個行程前要決定下一筆輸出的排序資料,必須作 k-1次比較才可以得到答案。

Page 77: 親愛的老師您好

77

7-3 外部排序法 多相合併法 下圖共有 21 個 runs,使用 2-way合併及 3個磁帶 T1、 T2、 T3來進行合併,假設這 21個行程 (已排序完畢,且令其長度為 1)的表示方為 Sn,其中 S 為行程大小, n 為長度相同 run的個數。例如 8個 runs且長度為 2,可表示成 28。

Page 78: 親愛的老師您好

78

7-3 外部排序法

使用 2-way 及 3 個磁碟的多項合併

Page 79: 親愛的老師您好

79

Q&A 討論時間

本章結束