//readstu2.c --- @copyLeft by tsaiwn@csie.nctu.edu.tw // 在 Unix 系統可以這樣: gcc readstu2.c; ./a.out // using struct, C version ==> NO default parameter // 注意已經去掉 default 參數, 所以這程式當作 C 是 OK 的 // 不過, 當作 C++ 當然也是 OK 啦! 可以在 Unix 這樣測試: // gcc readstu2.c; ./a.out // gcc -DGOOD readstu2.c; ./a.out // gcc -DGOOD -DDEBUG readstu2.c; ./a.out // gcc -DGOOD -DSPEED readstu2.c; ./a.out // gcc -DBEST -DSPEED readstu2.c; ./a.out #include #include #include #include #define FILE_NAME "studat.txt" const char* fileName = "studat.txt"; // 這樣也 OK // 用 Symbolic constant 符號常數比較不會不小心打字錯誤 // 輸入 3388 會採用 Selection sort with insertion (Stable版) #define MAGIC_NUMBER 3388 #define UNSTABLE_SEL 5566 #define BUBBLE_SORT 1895 #define INSERTION_SORT 2630 // 2008年11月12日 陳水扁被收押禁見, 編號2630號。 #define C_QSORT 258 // 聽說阿扁最新的編號是 1020 #define BUG_DEAD_POINT 43000 // 在我電腦上測試, quick sort 當 Stack 高度大於 43000 會死掉 /// typedef struct { // 這種寫法比較好 :-) long sid; // long 夠用來當交大學號 char name[9]; int s1, s2, s3, s4; // hwk*0.2, mid*0.3, quiz*0.1, fin*0.4 double final; // 學期成績 int rank; // 名次 } Student; // 從此可以過快樂的日子 :-) /// 一般是建議用像 Student_t 這種當作 type 名稱; 只是"建議" :-) // /// /// /// /// 因為只有一點點, 所以沒寫成 "student.h" 檔 Student x[99], stmp; int readStudent(Student * stu, FILE* fp); // read one student data void printStudent(Student stu, int finalFlag, int rankFlag); int getAllData(Student*x, char*fname); // 從檔案 fname 讀到 x[ ] void printALL(Student*,int); void sort(Student*,int,int, int whichSort); // default use quick sort void hint( ); void printSortMSG(int whichSort); // tell User what we are doing int countRecords(char* fname); /// /// /// void pHELP( ); void chop(char*s) { if(*s==0) return; // EMPTY string while(*s) ++s; --s; if(*s =='\n') *s = 0; } // chop( /// int nStu; // 記住總共幾個 records //Student y[88888]; // 這樣也可以, 但怎知 88888 夠不夠? Student * y; // used as y[ ]; // dynamic allocated (用malloc) void testALL(Student* x, int left, int right); int testFile(char*); int main( ) { long long tStart, tStop; double tRun; // use clock( ) in int whichSort; static char stmp[99]; /// if( testFile(FILE_NAME) != 0 ) return 38; // Error data file haha: // 偷懶讓我用 goto 一下 :-) hint( ); fprintf(stderr, " Enter the value : "); fgets(stmp, sizeof(stmp), stdin); chop(stmp); // 去掉尾巴的 New Line if there is one // 注意若不把尾巴的 '\n' 咬掉, 比字串會不成功! if( stmp[0] == '\?' || strcmp(stmp, "help")==0 || // 若先轉為大寫或小寫就更方便 strcmp(stmp, "HELP")==0 ) { pHELP( ); goto haha; } // 想一想, 若要弄成不用 goto whichSort = atol(stmp); // MAGIC_NUMBER (3388) for Selection sort /// nStu = countRecords(FILE_NAME); // 有多少 records ? y = (Student*)malloc( sizeof(Student) * (nStu+1) ); // C 用法 // y = new Student[nStu+1]; // C++ 這樣用比較好 if(y != 0) fprintf(stderr, " got memory OK...\n"); else { fprintf(stderr, "Not enough MEMORY?"); return 38; } /// 依據手冊, malloc 若傳回 0 表示沒有要到 memory getAllData(y, FILE_NAME); // read ALL students data into y[ ] fprintf(stderr, "Total %d records.\n", nStu); if(whichSort == 999) { testALL(y, 0, nStu-1); return 0; } /// /// 注意這 999 是 special case fprintf(stderr, "Before sorting ...\n"); printALL(y, nStu); /// /// printSortMSG(whichSort); /// /// tell User what Algorithm used tStart = clock( ); sort(y, 0, nStu-1, whichSort); // sort y[0] .. y[nStu-1] tStop = clock( ); tRun = (tStop - tStart)*1.0/CLOCKS_PER_SEC; fprintf(stderr, "\n ... sorting %d records ", nStu); fprintf(stderr, " done in %.3f seconds.\n", tRun); printALL(y, nStu); /// show time used again :-) fprintf(stderr, "\n Sorting %d records in %.3f seconds.\n", nStu, tRun); fprintf(stderr, "Hit ENTER key..."); getchar( ); return 0; } // main( // === === === main end === === === int countRecords(char* fname) { static char buf[999]; int n=0; FILE* fp = fopen(fname, "rt"); fgets(buf, sizeof(buf), fp); while(!feof(fp) ) { ++n; fgets(buf, sizeof(buf), fp); } fclose(fp); return n; } //countRecords( /// /// /// /// /// /// ///////////////////////////////// int getAllData(Student y[ ], char* fiName) { int i; // nStu is a global static char buf[999]; FILE* fp = fopen(fiName, "rt"); // read, text if(fp==0) { // open Fail fprintf(stderr, " File %s NOT found...", fiName); getchar( ); exit(38); } //============= for(i=0; i< nStu; ++i) readStudent(&y[i], fp); fclose(fp); return nStu; } int testFile(char* fileNAME) { FILE* fp; fp = fopen(fileNAME, "rt"); if(fp==0) { fprintf(stderr, "%s NOT found! ", fileNAME); getchar( ); return 38; } // if Fail to open ////////////////////////////// printf("Stu ID name hwk mid quiz fin 學期成績 名次\n"); readStudent( &x[0], fp); // 注意 "&" readStudent( &x[1], fp); // 類似 scanf 要傳 address 過去! readStudent( &stmp, fp); x[2] = stmp; // 哈哈! 這樣也可!! printf("The 2nd student (x[1]) :\n"); printStudent(x[1], 0, 0); // 若不能用default參數, 要加 ,0,0 printf("The 3rd student (x[2]) :\n"); printStudent(x[2], 1, 1); // 印學期成績, 與名次 fclose(fp); return 0; } //////// //////// //void sort(Student*x, int left, int right);;; int myCompare(const void*a, const void*b); void swapSelection(Student*x, int n); void insertionSort(Student*x, int n); /// //// 快速排序法, x[left] .. x[right] 要排好 void quickSort(Student x[], int left, int right, int(*comp)(const void*, const void*) ) { // 我寫成可以把比較函數當作參數傳給我 :-) // 注意, comp( ) 要照 Library qsort 規定的寫法! // 阿就是, 我會把兩項的起點當作指標傳給你!! static Student t; // 臨時變數 static int kkk=0, ttt=0; int i=left, j=right, flag= -1, middle; int kmr, klr,klm; // 用來決定是否調整 pivot 樞紐元素 // 簡易版用遠把 x[left] 當作 pivot #ifdef SPEED // small size speed up with calling swapSelection sort if(right - left < 15) { //insertionSort(&x[left], right-left+1); swapSelection(&x[left], right-left+1); return; // 因為資料很少時, quick sort 不會比較快! } // 意思是資料 < 15 個就直接叫別人做, 不再 recursive叫自己 // /// /// #endif #ifdef GOOD middle = (left+right)/2; // 把中間那個跟左邊對調來當作 pivot t = x[left]; x[left] = x[middle]; x[middle]=t; // 阿就是抓地理位置中間那個來當作 pivot 啦 #endif // /// gcc -DGOOD readstu2.c #ifdef BEST middle = (left+right)/2; kmr = comp((void*)&x[middle], (void*)&x[right]); klr = comp((void*)&x[left], (void*)&x[right]); /// 把第 2 大的抓來做 pivot; 就是 與 [left] 對調 if( (kmr > 0 && klr < 0) || (klr > 0 && kmr < 0) ) { t = x[left]; x[left] = x[right]; x[right]=t; }else { // 再來 check 看 middle 是否第二大? klm = comp((void*)&x[left], (void*)&x[middle]); if( (klm > 0 && kmr > 0) || (kmr < 0 && klm < 0) ) { t = x[left]; x[left] = x[middle]; x[middle]=t; }// miuddle 是 pivot }// if... #endif // /// gcc -DBEST readstu2.c #ifdef DEBUG ++kkk; ttt++; // 進入就加一, 以便估計 Stack 的高度 if( (kkk > BUG_DEAD_POINT) && ttt% 5 ==0) fprintf(stderr, " qs=%d, ", kkk); else if(kkk<10 || ttt%1000 ==0) // 免得印太多 fprintf(stderr, " qs=%d, ", kkk); #endif ////////////////////////////// // partition while(i < j) { // 注意改用 compare function if( comp((void*)&x[i], (void*)&x[j]) > 0 ) { //Ascending //if(x[i].sid > x[j].sid) { // 由小到大排 t = x[i]; x[i]=x[j]; x[j]=t; // swap(x[i], x[j]); flag = - flag; } if(flag > 0) ++i; else j--; //不是 i 往右, 就是 j 往左 }// while /// 這時候 i 指著pivot, x[i] 已經是在對的位置 ! if(left < i-1) quickSort(x, left, i-1, comp); // 左邊多於 1 個 if(i+1 < right) quickSort(x, i+1, right, comp); // 右邊多於 1 個 #ifdef DEBUG --kkk; // 離開就減一; 紀錄 stack 的高度, for Debug mode #endif return; } /// /// //////// === Stable Selection sort === //////// /// /// void selSort(Student*x, int n) { // 選到後, 用 insert 方式 // selection sort method, sort n records in array int x[ ]; // Ascending order 遞增++, this version is a STABLE one :-) // But 因選到後用"塞入"(INSERT), 要移動右邊全部, 比較慢! int i, k, min; Student tmp; // temporary data for(i = 0; i <= n-2; i++) { // 0.. n-2 min = i; // 假設我最小, 走遍 i-th 之後找出最小者 for(k=i+1; k <= n-1; ++k) { // 從第 i+1 個開始找 if(x[k].sid < x[min].sid) min = k; // 有人比我小, 記住它 } // step-2 insert that one into position i // move down all elemensts between x[i] and x[min-1] tmp = x[min]; // save the min one while(i < min) { // 別寫錯, min 每次減一到 i 右邊為止 x[min] = x[min-1]; // 例如 8--> 9, 7--> 8, 6--> 7, ... --min; } x[i] = tmp; // 它該在這位置 } // for i return; // done } // /// /// 以下是簡易選擇排序法 (選到後做對調) /// /// /// void swapSelection(Student*x, int n) { // swap version NOT stable // selection sort method, sort n records in array int x[ ]; // Ascending order 遞增++, this version is NOT a stable one :-) int i, k, min; Student tmp; for(i = 0; i <= n-2; i++) { // 0.. n-2 min = i; // 假設我最小, 走遍 i-th 之後的全部, 找出最小者 for(k=i+1; k <= n-1; ++k) { // 從第 i+1 個開始找 //if(x[k].sid < x[min].sid) min = k; // 有人比我小, 記住它 if( myCompare((void*)&x[k], (void*)&x[min]) < 0 ) min = k; }// for k // 注意我改用 compare function // step-2 --- swap i-th with the min one tmp = x[min]; x[min]=x[i]; x[i] = tmp; //對調的基本動作 } // for i return; // done } // /// /// /// /// /// /// /// /// /// /// /// /// /// void printALL(Student*x, int n) { // 前面5筆...後面5筆 int i; for(i=0; i<=5; ++i) printStudent(x[i], 1, 0); printf("...\n"); printf("Stu ID name hwk mid quiz fin 學期成績 名次\n"); for(i=nStu-5; i< nStu; ++i) printStudent(x[i], 1, 1); } // /// /// int readStudent(Student * stu, FILE* fp) { // default stdin // 注意 default 參數要 C++ 才有, 所以這程式要當作 C++ static char buf[99]; int nRead; fgets(buf, sizeof(buf), fp); nRead = sscanf(buf, "%ld %s %d %d %d %d", &(stu->sid), stu->name, &(stu->s1), &(stu->s2), &(stu->s3), &(stu->s4) ); stu->final = 0.2*stu->s1 + 0.3*stu->s2 + 0.1*stu->s3 + 0.4*stu->s4; stu->final = ((int)(100* stu->final +0.500001))/100.0; stu->rank = 1; // 大家都是第一名 :-) return nRead; } void printStudent(Student stu, int finalFlag, int rankFlag) { // 注意 default 參數要 C++ 才有, 所以這程式要當作 C++ // finalFlag !=0 ==> 印學期成績; rankFlag 要不要印名次 printf("%8d ", stu.sid); printf("%8s ", stu.name); printf("%3d %3d %3d %3d ", stu.s1, stu.s2, stu.s3, stu.s4); if(finalFlag) { printf("%6.2f ", stu.final); // 學期成績 if(rankFlag) { // 印名次, 須有印學期成績 printf("%5d", stu.rank); // note that stu is a struct } } // finalFlag printf("\n"); // done } /// /// ///=========================================================== // /// /// /// /// /// /// /// /// /// /// /// void bubbleSort(Student*x, int n) { // swap version NOT stable // sort n records in array int x[ ]; using Bubble sort // Bubble sort is always Stable // Ascending order 遞增 ++, 想一想如何改為遞減 ? int i, k, flag; Student tmp; for(i = 1; i < n; i++) { // 1, 2, .. , n-1 flag = 0; // assume already sorted for(k = 0; k < n-i; k++) { //0, 1, .. , n-i-1 if(x[k].sid > x[k+1].sid) { // 左大右小 要換 tmp = x[k+1]; x[k+1] = x[k]; x[k] = tmp; // swap flag = 1; // 有交換, 做個記號 ! } }//for k #ifndef NOFLAG /// gcc -DNOFLAG readstu2.c // 這樣不check FLAG 不會提前結束 if(flag == 0) break; // already done, 因為剛剛那趟沒有任何對調 #endif } // for i return; // done } // /// /// /// /// /// /// /// /// /// /// /// void insertionSort(Student*x, int n) { // 插入排列法 (插隊) // Insertion sort is always Stable /*** 注意 ㄝ 這使得下一句也被 comment 掉了 :) fprintf(stderr, "\n Insertion sort NOT implemented yet !\n\n"); /***/ int i, k, flag; Student tmp; // 做臨時變數用 for(i = 1; i < n; i++) { // 0-th already sorted tmp = x[i]; // 先站到一邊去 k = i-1; while( k >= 0 ) { if(x[k].sid <= tmp.sid) break; // ascending order x[k+1] = x[k]; // pull it down --k; }//while x[k+1] = tmp; // 站回該站的位置 } // for return; // done } /// /// ///=========================================================== // compare function for C Library function qsort( ) // also used in my quick sort, and in the swapSelection( ) /// int myCompare(const void*a, const void*b) { Student *p1 = (Student*)a; // 把指標 cast (轉型) 成 Student Student *p2 = (Student*)b; return (p1->sid - p2->sid); // for 由小排到大; 比 sid (學號) // return (*p1).sid - (*p2).sid; // for 由小排到大 // return -(p1->sid - p2->sid); // 若要相反順序 // return (p2->sid - p1->sid); // 若要相反順序 // return (*p2).sid - (*p1).sid; // 若要相反順序 } // /// void sort(Student* x, int left, int right, int kk) { // 由 kk 決定要使用哪個 sorting 方法 if(kk== MAGIC_NUMBER) selSort(x+left, right-left + 1); else if(kk== UNSTABLE_SEL) swapSelection(x+left , right-left + 1); else if(kk== BUBBLE_SORT) bubbleSort(x+left, right-left + 1); else if(kk== INSERTION_SORT) insertionSort(x+left, right-left + 1); else if(kk== C_QSORT) qsort((void*)(x+left), right-left + 1, sizeof(Student), myCompare); // 或寫這樣 (int(*)())myCompare else quickSort(x+left, left, right, myCompare); // 我寫的quick sort } // sort( ////////////////////////////////////////////////////////////////////// /* qsort(3) manual page (在 Unix 打入 "man qsort") 注意上列的 "(3)" 表示第三章, 阿就是 C 的 Library functions! 第二章 (2) 則是 System call (系統呼叫), 看起來則跟其他 functions 一樣:-) Description void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); where nmemb is number of elements in base[ ]; size is the size of one element in byte; compar is a call back compare function. qsort will call compar(void* first, void* second) and you should write the compar to return a value that one of this three: < 0, == 0, > 0 In general, you can simply do this: return (*first).key - (*second).key; ***/ void hint( ) { // 告訴使用者可以做啥事啦 /******* fprintf(stderr, " g++ -DNOFLAG 使得 bubble sort 不使用 FLAG提前\n"); fprintf(stderr, " g++ -DDEBUG 使得 quick sort 印debug訊息。\n"); fprintf(stderr, " g++ -DGOOD 使得 quick sort 有調整 pivot 元素。\n"); fprintf(stderr, " g++ -DSPEED 使得 quick sort 小size會叫別的 sort。\n"); fprintf(stderr, " g++ -DGOOD -DSPEED -DDEBUG readstu2.c\n"); ******/ fprintf(stderr, "\n本範例只做依學號遞增排序 ..\n"); fprintf(stderr, " Enter %d for Selection sort\n", MAGIC_NUMBER); fprintf(stderr, " %d for UNStable Selection sort\n", UNSTABLE_SEL); fprintf(stderr, " %d for C_built-in qsort\n", C_QSORT); fprintf(stderr, " %d for Bubble sort\n", BUBBLE_SORT); #ifndef NOFLAG fprintf(stderr, " Bubble sort has FLAG to force terminate.\n"); #else fprintf(stderr, " Bubble sort without FLAG, 沒提前結束, 比較慢.\n"); #endif fprintf(stderr, " %d for Insertion sort\n", INSERTION_SORT); fprintf(stderr, " other value for quick sort.\n"); #ifdef GOOD fprintf(stderr, " quick sort does adjust Pivot element(-DGOOD).\n"); #else #ifdef BEST fprintf(stderr, " quick sort is BEST Pivot version.\n"); #else fprintf(stderr, " quick sort is simple version.\n"); #endif #endif #ifdef SPEED // small size fprintf(stderr, " quick sort will CALL swapselection when small.\n"); #endif #ifdef DEBUG fprintf(stderr, " DEBUGging mode is ON !!!\n"); #endif } // hint( ) /////////////////////////// void printSortMSG(int whichSort) { fprintf(stderr, " .. sorting ..."); switch(whichSort) { case MAGIC_NUMBER: fprintf(stderr, " ..STABLE Selection sort ..."); break; case UNSTABLE_SEL: fprintf(stderr, " ..Unstable Selection sort ..."); break; case C_QSORT: fprintf(stderr, " ..C_Built-in qsort ..."); break; case BUBBLE_SORT: fprintf(stderr, " ..Bubble sort ..."); #ifndef NOFLAG fprintf(stderr, ", has FLAG to force terminate..."); #else fprintf(stderr, ", without FLAG, 沒提前結束, 比較慢..."); #endif break; case INSERTION_SORT: fprintf(stderr, " ..Insertion sort ..."); break; default: fprintf(stderr, " ..Quicksort ..."); #ifdef GOOD fprintf(stderr, "\n , with adjusting Pivot.."); #endif #ifdef SPEED // small size fprintf(stderr, " , small size acceleration..."); // /// /// #endif break; } // switch return; } // printSortMSG( ) void pHELP( ) { fprintf(stderr, " gcc -DNOFLAG 使得 bubble sort 不使用 FLAG提前" "\n gcc -DDEBUG 使得 quick sort 印debug訊息。\n"); fprintf(stderr, " gcc -DGOOD 使得 quick sort 有調整 pivot 元素。" "\n gcc -DSPEED 使得 quick sort 小size會叫別的 sort。" "\n gcc -DGOOD -DSPEED -DDEBUG readstu2.c\n"); fprintf(stderr, "Please enter a value, 請依照指示輸入一個整數,\n" " 請注意, 不同數值會使程式使用不同演算法做排序(Sort).\n" " quick sort 有兩個, 一個自己寫的一個是使用程式庫 qsort( )\n" " 另外, if you Enter 999, will test ALL 會測全部演算法!\n" ); }// pHELP( void testALL(Student* x, int left, int right) { int kSort[ ] = {C_QSORT, INSERTION_SORT, UNSTABLE_SEL, MAGIC_NUMBER, BUBBLE_SORT, 13958 }; // 13958 因不在前述各號碼內, 結果是使用我寫的 quick sort ! // 注意, 用符號常數比較不會不小心寫錯! // MAGIC_NUMBER 是使用 insertion 版的 Selection sort (選擇排序法) const int n = (sizeof kSort/ sizeof kSort[0]); long long tStart, tStop; double tRun; int i, k; Student* y = (Student*)malloc( sizeof(Student) * (nStu+1) ); for(i=0; i < n; i++) { // 照 kSort[ ] 內依序測各個 sorting 方法 // 每次都用同樣的 data (原先在 x[ ]內, copy 到 y[ ]) // 也可以使用 memcpy(y, x, nStu * sizeof(Student)); for(k=0; k < nStu; k++) y[k] = x[k]; // copy to y[ ] fprintf(stderr, "Before sorting ...\n"); printALL(y, nStu); /// /// printSortMSG(kSort[i]); /// /// tStart = clock( ); sort(y, 0, nStu-1, kSort[i]); tStop = clock( ); tRun = (tStop - tStart)*1.0/CLOCKS_PER_SEC; fprintf(stderr, "\n ... sorting %d records ", nStu); fprintf(stderr, " done in %.3f seconds.\n", tRun); printALL(y, nStu); /// show time used again fprintf(stderr, "\n Sorting %d records in %.3f seconds.\n", nStu, tRun); fprintf(stderr, "Hit ENTER key..."); getchar( ); /// }//for i }// testALL( /******************* D:\test> path C:\Dev-Cpp\bin;%path% D:\test> gcc -DGOOD readstu2.c -o sortstu.exe D:\test> gcc readstu2.c -o sortqqq.exe D:\test> gcc -DDEBUG -DNOFLAG readstu2.c -o sortdebug.exe D:\test> dir so*exe sortdebug.exe sortqqq.exe sortstu.exe 3 個檔案 71,639 位元組 D:\test> D:\test> gcc -DBEST -DSPEED readstu2.c -o sortbest.exe D:\test> gcc genscore.c -o gens.exe D:\test> gens.exe wait...File studat.txt generated successfully. Total 81482 student records. D:\test> sortqqq D:\test> sortdebug D:\test> sortstu 補充說明: 編譯器 (Compiler) 看到 -DGGYY 時, 會在程式的最前面加入這列: #define GGYY 如果用 -DHA=hehe 則加入這: #define HA hehe 若用 -DQQ=3388 則加入這: #define QQ 3388 這樣我們可以在程式中用"條件編譯"(conditional compiling) 例如, #ifdef GGYY 或是, #ifndef GGYY 注意, # 開頭的 #if, #else, #endif 都是給 Compiler 看的! 這招常用在印出除錯(Debug)訊息, 例如你懷疑程式有問題, 不想努力學 Debugger (如 gdb) 的用法, 可以這樣: #ifdef DEBUG printf("...."); //... #endif 然後正常編譯等於這段的 printf 沒有寫! 但若這樣 gcc -DDEBUG file.c 則等於這段的 printf 有寫! 如果還不太懂, 請用 gogle.com 查詢 "C 條件編譯" *******************************/