1 泡沫排序(Bubble Sort)基本邏輯:相鄰比較與交換
泡沫排序是最基礎的排序法:每一輪都掃過整個(或剩餘)陣列,只要相鄰兩個元素順序不對就交換,反覆進行直到整個陣列排序完成。跑完 n-1 輪之後,最大的幾個元素就會像泡泡一樣依序「浮」到陣列尾端。
#include <stdio.h>
void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
int main(void) {
int arr[5] = {5, 2, 4, 1, 3};
for (int i = 0; i < 5 - 1; i++)
for (int j = 0; j < 5 - 1 - i; j++)
if (arr[j] > arr[j + 1])
swap(&arr[j], &arr[j + 1]);
for (int i = 0; i < 5; i++) printf("%d ", arr[i]); // 1 2 3 4 5
printf("\n");
return 0;
}
想像每一輪都是「相鄰兩人比身高,矮的往前站、高的往後站」,一輪比下來,全場最高的那個人一定會被擠到隊伍最後面——這就是「最大值像泡泡一樣浮到陣列尾端」的意思。跑滿 n-1 輪,就能保證所有人都排好隊。
2 選擇排序(Selection Sort):對照另一種排序策略
選擇排序每一輪都先「選出」剩餘範圍中最小的元素,再跟目前位置交換一次;跟泡沫排序不同,選擇排序每一輪只交換一次(泡沫排序每一輪可能交換很多次)。
#include <stdio.h>
int main(void) {
int arr[5] = {5, 2, 4, 1, 3};
for (int i = 0; i < 5; i++) {
int min = i;
for (int j = i + 1; j < 5; j++)
if (arr[j] < arr[min]) min = j;
int t = arr[i]; arr[i] = arr[min]; arr[min] = t;
}
for (int i = 0; i < 5; i++) printf("%d ", arr[i]); // 1 2 3 4 5
printf("\n");
return 0;
}
| 比較項目 | 泡沫排序 | 選擇排序 |
|---|---|---|
| 每一輪動作 | 相鄰兩兩比較,順序不對就交換 | 掃描找出最小值,只跟目前位置交換一次 |
| 每一輪交換次數 | 可能很多次 | 最多1次 |
| 共同點 | 都需要外層+內層兩層迴圈,跑完後陣列排序完成 | |
3 排序演算法的比較次數:n(n-1)/2 的由來
對 n 筆資料進行標準泡沫排序或選擇排序,兩兩比較的總次數是一個等差數列求和:第1輪比較 (n-1) 次,第2輪比較 (n-2) 次……最後一輪比較1次,加總起來就是 n(n-1)/2。
| 輪次 | 比較次數 |
|---|---|
| 第1輪 | n-1 次 |
| 第2輪 | n-2 次 |
| …… | …… |
| 最後一輪 | 1 次 |
| 總計 | (n-1)+(n-2)+...+1 = n(n-1)/2 |
算出 n(n-1) 之後別忘了除以2——如果沒有除以2,等於把每一對資料重複算了兩次(一次算A跟B比、一次又算B跟A比,但這其實是同一次比較)。也別誤以為每一輪都要固定比較n次(忽略了範圍會隨排序進行逐輪縮小)。
4 交換函式必須用指標,傳值無法真正交換
交換兩個數值的函式若用一般變數(傳值)當參數,函式內的交換只會影響函式內的複本,不會真的改到呼叫端的陣列;必須改用指標(傳址)才能真正交換原始資料。
void swap_wrong(int a, int b) { int t=a; a=b; b=t; } // 傳值:呼叫端不會被真的交換
void swap_right(int *a, int *b) { int t=*a; *a=*b; *b=t; } // 傳指標:才能真的交換
如果 swap() 的參數宣告是一般 int,呼叫時卻傳入一個指標運算式(例如 numbers+i,型態是 int*),編譯器會直接報型態不符的錯誤(expected 'int' but argument is of type 'int *'),這不是邏輯錯誤,而是編譯期就會抓到的型態不匹配錯誤。
5 追蹤複雜的雙層迴圈排序邏輯:逐輪手寫陣列狀態
統測有些排序追蹤題,寫法跟教科書上標準的泡沫排序或選擇排序不完全一樣(例如比較與交換的範圍、時機被稍微改寫過)。遇到這種題目,務必逐行手寫每一輪比較與交換後的陣列狀態,不要用直覺猜答案——直覺很容易被「看起來像」某種標準排序法給誤導。
| 步驟 | 陣列狀態 | 這一步做了什麼 |
|---|---|---|
| 初始 | [5, 2, 4] | — |
| 比較① | [2, 5, 4] | 5>2,交換位置0、1 |
| 比較② | [2, 4, 5] | 5>4,交換位置1、2 |
先確認每一次 if 判斷式比較的究竟是「相鄰兩個」還是「當下位置跟目前找到的最小值」,再確認交換是「符合條件就立刻交換」還是「掃完整個範圍才交換一次」——這兩種設計的最終結果可能差異很大,務必依實際程式碼逐步模擬,不要套用自己以為的「標準寫法」。
6 常考陷阱總複習:型態不符的函式呼叫錯誤
排序題常會考「把兩兩交換的程式碼改寫成呼叫一個 swap() 函式」,這時最容易出現的錯誤,就是呼叫時傳入的引數型態跟函式參數宣告的型態不一致。
void swap(int a, int b){ // 參數是int,要求傳值
int tmp;
tmp=a; a=b; b=tmp;
}
// ......
swap(numbers+i, numbers+min); // ❌ numbers+i 是 int*,跟參數要求的int型態不符
1. 引數裡出現了 陣列名稱+索引 這種指標算術運算式,但函式參數宣告的是一般型態(不是指標)。
2. 錯誤訊息明確提到「expected 'int' but argument is of type 'int *'」這類型態不符的字樣。
3. 想要函式真正交換陣列裡的資料,卻沒有把函式參數宣告成指標型態。
7 111~115 歷屆統測真題全選項深度剖析(共 2 題)
以下收錄統測專業科目(二)近年全部 2 題排序演算法真題。每題均提供高解析度原始考題掃描圖、互動選項按鈕與所有選項逐項深入解析:
#include <stdio.h>
#define N 11
void swap(int a, int b){
int tmp;
tmp=a;
a=b;
b=tmp;
}
void main(void){
int numbers[N]={1,3,5,7,9,2,4,6,8,0,'a'};
int tmp, i, min;
for(min=0; min<N; min++)
for(i=0; i<N; i++){
if(numbers[i]<numbers[min]){
tmp=numbers[min];
numbers[min]=numbers[i];
numbers[i]=tmp;
}
}
for(i=0; i<N; i++){
printf("%d ", numbers[i]);
}
}
🔍 四個選項逐項深度剖析
int numbers[N],'a'這個字元常值存入int陣列時會被轉換成它的ASCII碼97(一個普通整數),程式用 %d 格式化輸出,印出來的會是97這個數字,不會是字元'a'本身。numbers[i]<numbers[min] 就立刻交換,這個「持續尋找更小值、立刻交換到min位置」的機制,會逐步把當下找到的最小值往前擠到min這個位置,其餘的值依序被擠成遞減排列。實際模擬全部11輪,最終陣列變為[97,9,8,7,6,5,4,3,2,1,0],選 (C)。void swap(int a, int b){
int tmp;
tmp=a; a=b; b=tmp;
}
void main(void){
int numbers[N]={1,3,5,7,9,2,4,6,8,0,'a'};
int tmp, i, min;
for(min=0; min<N; min++)
for(i=0; i<N; i++){
if(numbers[i]<numbers[min]){
swap(numbers+i, numbers+min); // 啟用此行呼叫
}
}
for(i=0; i<N; i++)
printf("%d ", numbers[i]);
}
🔍 四個選項逐項深度剖析
void swap(int a, int b),要求傳入 int(一般整數值,傳值呼叫);但呼叫時傳入的 numbers+i、numbers+min 是指標算術運算的結果,型態是 int*(指向int的指標),跟函式參數要求的 int 型態不符,編譯器才會報出 expected 'int' but argument is of type 'int *' 這個型態不匹配的錯誤,選 (A)。numbers+i)是完全合法的指標算術,用來取得陣列中第i個元素的位址,這個語法本身並沒有錯誤,錯誤訊息指出的問題也不是「不能相加」,而是傳入函式時的型態不符。'a'(值97,是陣列的其中一個初始值)跟 swap() 函式參數名稱 a 是完全不同層級的東西(一個是字元常值,一個是變數名稱),兩者不會產生任何命名衝突,這跟編譯器實際回報的型態不符錯誤訊息也完全對不上。for(min=0; min<N; min++) 中會被明確賦值後才使用,並沒有「沒有初始值」的問題;而且編譯器回報的錯誤訊息明確是關於引數型態不符(int 與 int*),跟 min 有沒有初始值無關。8 高職段考與模擬精選實戰題(5 題全選項解析)
透過以下 5 題精選模擬試題,全面檢驗你對泡沫排序、選擇排序、比較次數與傳值/傳指標交換的掌握度:
int arr[4] = {4, 1, 3, 2};
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3 - i; j++)
if (arr[j] > arr[j+1]) {
int t = arr[j]; arr[j] = arr[j+1]; arr[j+1] = t;
}
printf("%d %d %d %d", arr[0], arr[1], arr[2], arr[3]);
🔍 四個選項逐項深度剖析
arr[j] > arr[j+1](前面比後面大才交換,讓較大的值持續往後移),這會產生遞增排序,不是遞減排序。int arr[4] = {30, 10, 40, 20};
for (int i = 0; i < 4; i++) {
int min = i;
for (int j = i+1; j < 4; j++)
if (arr[j] < arr[min]) min = j;
int t = arr[i]; arr[i] = arr[min]; arr[min] = t;
}
printf("%d %d %d %d", arr[0], arr[1], arr[2], arr[3]);
🔍 四個選項逐項深度剖析
arr[j] < arr[min](找剩餘範圍中最小的),每輪選出最小值放到前面,會產生遞增排序,不是遞減排序。🔍 四個選項逐項深度剖析
(n-1)+(n-2)+...+1 = n(n-1)/2。n=6時,比較次數為 6×5/2=15,選 (B)。n(n-1) 算出來了(6×5=30)卻忘記除以2,也就是把每一對資料重複算了兩次(一次算A跟B比、一次又算B跟A比,但實際上這是同一次比較,不該算兩次)。void swap_bad(int a, int b) { int t = a; a = b; b = t; }
int main(void) {
int x = 1, y = 2;
swap_bad(x, y);
printf("%d %d", x, y);
return 0;
}
🔍 四個選項逐項深度剖析
void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
int main(void) {
int x = 5, y = 9;
swap(&x, &y);
printf("%d %d", x, y);
return 0;
}
🔍 四個選項逐項深度剖析
int *a, int *b,呼叫時傳入 &x、&y,屬於傳址呼叫。函式內用 *a、*b 解參考,直接存取並交換x、y實際的記憶體內容,是真正的交換,執行後x變成原本y的值9,y變成原本x的值5,輸出"9 5",選 (A)。*a(x的值),再把*b(y的值)指定給*a,最後把t(x的舊值)指定給*b,這是標準的三步驟交換,x跟y最終應該各自持有對方原本的值,不會兩個變得相同。9 考前衝刺 10 秒速記口訣 & 核心知識檢核清單
泡沫:相鄰兩兩比較、每輪可能多次交換;選擇:找最小值、每輪只交換一次。
n筆資料兩兩比較總次數是n(n-1)/2,別忘了除以2,也別誤用固定n次估算。
swap函式參數若是一般int(傳值),呼叫端資料不會被真正交換,要用指標參數。
傳入陣列指標運算式(如numbers+i)給宣告成int的參數,會直接編譯錯誤。
比較與交換的範圍、時機若跟教科書寫法不同,要逐步模擬,不要用直覺猜答案。