---
# System prepended metadata

title: Algorithm - sorting - counting sort
tags: [演算法優化, sorting]

---

# 演算法 - Counting Sort


## 輸入格式
```
第一行：一個整數 N（1 ≤ N < 20000），表示陣列中的元素個數  
第二行：N 個以空格分隔的整數
```

---

##  輸出格式
```
排序後的 N 個整數，以空格分隔
```

---

##  演算法設計思路
遇到這類問題，我有兩種想法 ：

- **Merge Sort**  
  - 當 N < 40000 時，Merge Sort 最差情況為 $O(N^{1.3})$，比 Quick Sort、Shell Sort 的 $O(N \log N)$ 更快。

- **Counting Sort**  
  - 在數據集中時表現非常好，時間複雜度為 $O(N + k)$。  
  - 但若測資中有極大或極小值，導致 k 過大，則效能下降。

經過測試後，我這一次遇到的數字範圍約在 -22000 ~ 22000，所以我選擇使用Counting sort 來做！ 

---

##  使用 Counting Sort：字元範例

### 輸入範例：
```
e a a b d e c c
```

---

###  排序流程圖解：

#### Step 1：讀入字元 `e`，count['e']++
![step_01](https://hackmd.io/_uploads/r13ltOlexx.png)

#### Step 2：讀入字元 `a`，count['a']++
![step_02](https://hackmd.io/_uploads/SkJZFOgegl.png)

#### Step 3：讀入字元 `a`，count['a']++
![step_03](https://hackmd.io/_uploads/S1-WYdlele.png)

#### Step 4：讀入字元 `b`，count['b']++
![step_04](https://hackmd.io/_uploads/SkHbKulllg.png)

#### Step 5：讀入字元 `d`，count['d']++
![step_05](https://hackmd.io/_uploads/SyFWtOxeeg.png)

#### Step 6：讀入字元 `e`，count['e']++
![step_06](https://hackmd.io/_uploads/HyeGK_egel.png)

#### Step 7：讀入字元 `c`，count['c']++
![step_07](https://hackmd.io/_uploads/r1XzFOxgee.png)

#### Step 8：讀入字元 `c`，count['c']++
![step_08](https://hackmd.io/_uploads/ByDGKuxlxg.png)

#### Step 9：計算前綴和 start[]
![step_09](https://hackmd.io/_uploads/rk2Mtuxggl.png)

#### Step 10：計算累加值 start + count
![step_10](https://hackmd.io/_uploads/SyxQY_lggg.png)

---

###  排序結果：
```
索引：    0   1   2   3   4   5   6   7  
內容：    a   a   b   c   c   d   e   e
```

---

##  原始 Java 程式碼（使用 Scanner）

```java
import java.util.Scanner;

public class Main {
    static final int size = 44000; 
    static final int OFFSET = 22000;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] number = new int[n];
        int[] count = new int[size];
        int[] start = new int[size];
        int[] output = new int[n];

        for (int i = 0; i < n; i++) {
            number[i] = sc.nextInt();
            count[number[i] + OFFSET]++;
        }

        for (int i = 0; i < size - 1; i++) {
            start[i + 1] = start[i] + count[i];
        }

        for (int i = 0; i < n; i++) {
            output[start[number[i] + OFFSET]++] = number[i];
        }

        for (int i = 0; i < n; i++) {
            System.out.print(output[i] + " ");
        }
    }
}
```

---

##  原始版本的缺點

- 使用 `Scanner` + `System.out.print()` 效率低
- 對於大量輸入輸出，執行速度慢

---

##  第一版優化：改用高速 I/O

### 優化方式：
- 輸入：改用 `System.in.read()` 搭配 buffer
- 輸出：改用 `System.out.write()` 及自製 `writeNum()`

```java 
public class Main {
    static final int SIZE = 44000;
    static final int OFFSET = 22000;
    static final int MAX = 20000;

    static final int[] count = new int[SIZE];
    static final int[] start = new int[SIZE];
    static final int[] number = new int[MAX];
    static final int[] output = new int[MAX];

    static final byte[] buf = new byte[1 << 20]; // 輸入緩衝 (2^20byte 約1MB)
    static int ptr = 0, len = 0;

    static final byte[] outbuf = new byte[1 << 23]; // 輸出緩衝 (2^23 約8MB)
    static int outptr = 0;

    public static void main(String[] args) throws Exception {
        len = System.in.read(buf);
        int n = readInt();

        for (int i = 0; i < n; i++) {
            number[i] = readInt();
            count[number[i] + OFFSET]++;
        }

        for (int i = 0; i < SIZE - 1; i++) {
            start[i + 1] = start[i] + count[i];
        }

        for (int i = 0; i < n; i++) {
            int val = number[i];
            int idx = start[val + OFFSET]++;
            output[idx] = val;
        }

        for (int i = 0; i < n; i++) {
            writeNum(output[i]);
        }

        System.out.write(outbuf, 0, outptr);
    }

    static int readInt() {
        int val = 0;
        byte b = buf[ptr++];
        while (b <= ' ') b = buf[ptr++]; // 遇到空格跳過

        boolean negative = (b == '-'); // 判斷是否為負數
        if (negative) b = buf[ptr++];    // 跳過負號

        do {
            val = (val << 3) + (val << 1) + (b & 15); //使用二進位做 val*10+b 
     // val << 3 = val * 2 ^ 3 = val * 8
     // val << 1 = val * 2 ^ 1 = val * 2
     // b & 15 二進制ASCII轉數字取後四位 所以b and 15(1二進制 = 00001111) 即可轉為數字
        
            if (ptr >= len) break;
            b = buf[ptr++];
        } while (b >= '0');

        return negative ? -val : val; 
    }

    static void writeNum(int num) {
        if (num < 0) {
            outbuf[outptr++] = '-'; 
            num = -num;
        }

        int tmpPtr = outptr;
        int digits = 0;

        do {
            outbuf[outptr++] = (byte) ('0' + num % 10);
            num /= 10;
            digits++;
        } while (num > 0);

        // 反轉數字部分
        for (int i = 0; i < digits / 2; i++) {
            byte temp = outbuf[tmpPtr + i];
            outbuf[tmpPtr + i] = outbuf[tmpPtr + digits - 1 - i];
            outbuf[tmpPtr + digits - 1 - i] = temp;
        }

        outbuf[outptr++] = ' ';
    }
}
```

---

###  效能阻礙分析

```java
        for (int i = 0; i < n; i++) {
            number[i] = readInt();
            count[number[i] + OFFSET]++;
        }

        for (int i = 0; i < SIZE - 1; i++) {
            start[i + 1] = start[i] + count[i];
        }

        for (int i = 0; i < n; i++) {
            int val = number[i];
            int idx = start[val + OFFSET]++;
            output[idx] = val;
        }

        for (int i = 0; i < n; i++) {
            writeNum(output[i]);
        }
```

- 共使用 4 個 for 迴圈
- 空間使用量高：number[], output[], start[]
- 時間複雜度為：**2n + k**

---

##  第二版優化：節省空間 + 非穩定排序

### 核心優化重點：

- 使用 `short[] count` 節省記憶體
- 不使用 `start[]`、`output[]`、`number[]`
- 不保證穩定排序，但大幅提升效能

```java
class Main {
    static final int SIZE = 44000;
    static final int OFFSET = 22000;
    static final short[] count = new short[SIZE];

    static final int BUF_SIZE = 1 << 19;
    static final byte[] buf = new byte[BUF_SIZE];
    static int ptr = 0, len = 0;

    static final byte[] outbuf = new byte[1 << 23];
    static int outptr = 0;
    static final byte[] numbuf = new byte[20]; 

    public static void main(String[] args) throws Exception {
        len = System.in.read(buf);
        int n = readInt();

        for (int i = 0; i < n; i++) {
            int x = readInt();
            int idx = x + OFFSET;
            if (count[idx] != 65535) count[idx]++;
        }

        int printed = 0;
        for (int i = 0; i < SIZE && printed < n; i++) {
            int times = count[i] & 0xFFFF;
            while (times-- > 0) {
                writeInt(i - OFFSET);
                printed++;
                if (printed == n) break;
            }
        }

        System.out.write(outbuf, 0, outptr);
    }

    static int readInt() {
        int x = 0;
        int b = buf[ptr++];
        while (b <= ' ') b = buf[ptr++];

        boolean neg = (b == '-');
        if (neg) b = buf[ptr++];

        while (b >= '0') {
            x = (x << 3) + (x << 1) + (b & 15);
            b = buf[ptr++];
        }
        return neg ? -x : x;
    }

    static void writeInt(int x) {
        if (x == 0) {
            outbuf[outptr++] = '0';
            outbuf[outptr++] = ' ';
            return;
        }

        if (x < 0) {
            outbuf[outptr++] = '-';
            x = -x;
        }

        int p = 0;
        while (x > 0) {
            numbuf[p++] = (byte) (x % 10 + '0');
            x /= 10;
        }
        while (p-- > 0) {
            outbuf[outptr++] = numbuf[p];
        }
        outbuf[outptr++] = ' ';
    }
}
```

---

##  兩版比較總表

| 項目             | 第一版（穩定） | 第二版（非穩定） |
|------------------|----------------|------------------|
| 穩定排序         | ⭕ 是           | ❌ 否             |
| 陣列使用         | 多             | 少（僅 count[]）  |
| 記憶體佔用       | 高             | 低               |
| 實際時間複雜度   | O(2n + k)       | O(n + k)          |
| 適合場景         | 功能正確、教學 | 競賽、效能極限場景 |

---

##  總結建議

| 使用情境             | 建議使用版本   |
|----------------------|----------------|
| 需要穩定排序         | 第一版         |
| 極限效能需求         | 第二版         |
| 小型資料測試         | 第一版         |
| 線上比賽、大資料輸入 | 第二版         |

---
有任何建議、想法歡迎跟我說！！！