11.6 自定義比較函式

greater() 解決了「由大到小」,但排序規則千奇百怪:按絕對值排、按個位數排、偶數優先……這些 sort 都做得到——把你自己寫的比較函式當第三個參數傳進去。

比較函式的規格:

  • 恰有兩個參數,型態就是被排序元素的型態;回傳 bool
  • 語意只有一句話:cmp(x, y) 回答「x 是否必須排在 y 前面」
    • 必須在前 → 回傳 true
    • x 與 y 相等,或 x 該排在後面 → 回傳 false

例如「由大到小」的比較函式,用這句話翻譯:x 比 y 大時,x 必須在前——

bool cmp(int x, int y) {
    if (x > y) return true;    // x 比 y 大:x 必須排在 y 前面
    return false;              // 相等或 x 較小:回傳 false
}

if 的條件本身就是要回傳的布林值,所以可以直接簡寫(上冊 7.4 判斷函式的老技巧):

bool cmp(int x, int y) {
    return x > y;
}

完整示範:

#include <iostream>
#include <algorithm>
using namespace std;

bool cmp(int x, int y) {
    return x > y;      // x 比 y 大時,x 必須排在 y 前面 → 由大到小
}

int main() {
    int a[5] = {3, 1, 4, 1, 5};
    sort(a, a + 5, cmp);               // 用自己的規則排序
    for (int i = 0; i < 5; i++) {
        cout << a[i] << ' ';
    }
    cout << '\n';
    return 0;
}

執行結果:

5 4 3 1 1

各種規則,一招打天下

按絕對值由小到大abs 是上冊 7.6 的老朋友):

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

bool cmp(int x, int y) {
    return abs(x) < abs(y);    // 絕對值小的排前面
}

int main() {
    int a[5] = {-5, 2, -1, 4, -3};
    sort(a, a + 5, cmp);
    for (int i = 0; i < 5; i++) {
        cout << a[i] << ' ';
    }
    cout << '\n';
    return 0;
}

執行結果:

-1 2 -3 4 -5

按個位數由小到大% 取餘數,上冊 2.6):

#include <iostream>
#include <algorithm>
using namespace std;

bool cmp(int x, int y) {
    return x % 10 < y % 10;    // 個位數小的排前面
}

int main() {
    int a[5] = {31, 17, 25, 8, 42};
    sort(a, a + 5, cmp);
    for (int i = 0; i < 5; i++) {
        cout << a[i] << ' ';
    }
    cout << '\n';
    return 0;
}

執行結果:

31 42 25 17 8

比較函式裡可以讀全域陣列:排「編號」

一個很重要的進階用法:被排序的可以是「編號」,比較的依據放在全域陣列裡。例如身高存在全域的 height,我們排序的是編號 1 \sim 4

#include <iostream>
#include <algorithm>
using namespace std;

int height[5] = {0, 160, 155, 170, 158};   // height[i]=編號 i 的身高(1-base)

bool cmp(int x, int y) {
    return height[x] < height[y];   // 比的不是編號本身,是編號對應的身高
}

int main() {
    int id[4] = {1, 2, 3, 4};       // 要排序的是「編號」
    sort(id, id + 4, cmp);
    for (int i = 0; i < 4; i++) {
        cout << id[i] << ' ';
    }
    cout << '\n';
    return 0;
}

執行結果:

2 4 1 3

編號 2 的身高 155 最矮,所以排最前面。這招的妙處:原始資料完全沒被動過,排好的是「觀看順序」——想再按體重排一次?換一個比較函式、再排一次編號就好,同一份資料要幾種排法有幾種排法。

動手試試看:先解特殊數字排序——題目直接給了主程式,把 cmp 補完就好(偶數優先、偶數由小到大、奇數由大到小:先判斷兩數的奇偶是否相同,再分情況比較)。再挑戰兩種排序——同一批人按身高、按體重各排一次,正是「排編號」的實戰;注意題目對「一樣高/一樣重」的規則,把它寫進比較函式裡(比如身高相同時編號大的在前,就再比一次編號)。