基礎排序

 實習講師:陳奕瑄

 Credit:蔡銘軒

為什麼要排序?

因為排序很基本、很常用、很重要

而且第二階段考會考

假設你是

健康與體育小老師…

你拿到一疊健康檢查單,二十八歲單身徵婚中的女老師要你把大家的健康檢查單由身高矮到高排序。

你會怎麼排序?

一張一張看然後把單子插入對應的位置?

先分類再一張一張看?

叫隔壁同學幫你排?

隔壁同學用EXCEL排,明顯是個狠角色。

像我們這種玩資芽的齁,自己寫

 code 排序也是很正常的啦。

那電腦會怎麼排序?

電腦不會 "看" 也不會 "插",

要明確告訴他怎麼做

以下提到“排序”都預設是

將陣列裡的元素按照由小到大的順序排好

基本想法:比大小

如果有兩個數字a, b在陣列裡,a比b大但a卻出現在b的前面,我們就把他們交換

void swap(int &a, int &b) {
    int c = a;
    a = b;
    b = c;
}

一直交換下去,總有一天會排序好。

那麼我們要怎麼有系統地交換元素呢?

Bubble Sort

在上Code之前,先來看看它的概念吧!

概念:

每次操作從index小的元素開始往後,依次檢查相鄰的兩個元素,如果大小順序顛倒,就交換!

不斷重複這樣的步驟,直到排序完成。

來看點例子更好懂!

Bubble Sort Visualizer

Bubble Sort

void bubble_sort(int arr[], int n) {
    // 最多跑n - 1次迴圈就會sort完整個陣列
    for (int i = 0; i < n - 1; i++)
        for (int j = 0; j < n - i - 1; j++)  
            // 如果相鄰元素順序不對,就交換 
            if (arr[j] > arr[j + 1])
                swap(arr[j], arr[j + 1]); 
}

時間複雜度

外層的迴圈:

內層的迴圈:

交換:

總共:

O(n)
O(n)
O(1)
O(n^2)

best case/worst case都是

O(n^2)

Bubble Sort

void bubble_sort(int arr[], int n) {
    // 最多跑n - 1次迴圈就會sort完整個陣列
    for (int i = 0; i < n - 1; i++) {
    	bool has_swap = false; // 用來紀錄這一次迴圈有沒有發生交換
        for (int j = 0; j < n - i - 1; j++) {
            // 如果相鄰元素順序不對,就交換 
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
                has_swap = true; // 發生交換了
            }
        }
        if (!has_swap) // 如果沒有發生交換,代表已經排好了
        	break;
    }
}

Best case: 

Worst case:

O(n)
O(n^2)

Selection Sort

在上Code之前,先來看看它的概念吧!

概念:

把陣列分成已排序未排序兩部分(一開始全部都是未排序)。

每次操作從未排序的部分找出最小值,把它跟未排序的第一個元素交換(效果相當於把這個元素接在已排序部分的最後面),並將這個元素歸類為已排序

重複直到排序完成。

來看點例子更好懂!

Selection Sort Visualizer

Selection Sort

時間複雜度?

外層的迴圈:

內層的迴圈:

交換:

總共:

O(n)
O(n)
O(1)
O(n^2)

best case/worst case都是

O(n^2)
void selection_sort(int arr[], int n) {
  // 最多做n - 1次迴圈就會排序好
  for (int i = 0; i < n - 1; i++) {
    int min_idx = i; // 用來記錄未排序部分的最小值
    for (int j = i + 1; j < n; j++)
      if (arr[j] < arr[min_idx]) // 發現更小的數值,更新
      	min_idx = j;
      swap(arr[min_idx], arr[i]); // 把未排序部分的最小值放到對的位置
  }
}

Insertion Sort

在上Code之前,先來看看它的概念吧!

概念:

把陣列分為已排序未排序兩部分(一開始第一個元素是已排序,其餘為未排序)。

每次操作選擇未排序部分的第一個元素,看看他應該被插在已排序部分的哪個位置,把它放到那裡去!

來看點例子更好懂!

Insertion Sort Visualizer

Insertion Sort

void insertion_sort(int arr[], int n) {
    // 最多跑n - 1次迴圈就會sort完整個陣列
    for (int i = 1; i < n; i++) {
    	key = arr[i]; // 這一輪要插入的數字
        j = i - 1;
        // 用while迴圈一邊平移元素,一邊找key該插在哪裡
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key; //將key放入該放的位置
    }
}

時間複雜度

外層的迴圈:總是

內層的迴圈:最佳              最差

交換:

總共:最佳               最差

O(n)
O(n)
O(1)
O(1)
O(n)
O(n^2)

練習

Sample Solution

#include <iostream>
void bubble_sort(int arr[], int n) {
  for (int i = 1; i < n - 1; i++) {
    	for (int j = 1; j < n - 1; j++) {
          if (arr[j] < arr[j + 1])
            	std::swap(arr[j], arr[j + 1]);
        }
    }
}
int main() {
    int n, arr[10000 + 5];
    std::cin >> n;
    for (int i = 0; i < n; i++)
    	std::cin >> arr[i];
    bubble_sort(arr, n);
    for (int i = 0; i < n; i++) {
    	std::cout << arr[i];
        if (i != n - 1)
        	std::cout << ' ';
        else
        	std::cout << std::endl;
    }
    return 0;
}

總結

  • bubble sort
  • selection sort
  • insertion sort

這三種排序演算法很容易出現                 的時間複雜度,在資料量較大時                          表現非常差

O(n^2)
(n>10^5)

以上介紹三種非常基本,基於交換的排序演算法

總結

有興趣的同學可以自行學習其他更有效率的排序演算法,例如:

Merge Sort:  

Quick Sort: worst case               ,但平均                        ,且實務上非常快

O(nlogn)
O(n^2)
O(nlogn)

總結

基於比較&交換的排序演算法,最好只能做到

O(nlogn)

也有非基於交換的排序演算法,例如:

Couting Sort:  

Radix Sort: 

O(n+k)
O(nlog(\max{a_0,\dots,a_{n-1}}))

但以上兩種排序法有使用條件的限制,並不是絕對較好。

有興趣的同學可以自行學習!

好用工具介紹

std::sort()

#include <iostream>
#include <algorithm>
#include <cstdlib>
void get_random_array(int arr[], int n) {
    srand(127);
    for (int i = 0; i < n; i++)
        arr[i] = rand();
}
int main() {
    int arr[100000 + 5];
    int n = 100000;
    get_random_array(arr, n);
    std::sort(arr, arr + n);
    for (int i = 0; i < n; i++)
    	std::cout << arr[i] << ' ';
    std::cout << std::endl;
    return 0;
}

作業

下課啦!

Made with Slides.com