Bubble Sort:

  泡泡排序其實非常簡單,把每一個數字想像成一個泡泡,數字的大小就是泡泡的重量,越重的會沉在越下面,那如果每次我們都把最重的沉下去,做了一定次數之後就排序完成了,如下圖。

  

  5是最重的泡泡,所以每一次跟周圍比都要往下沉,最後沈到最底下,下一次開始就是由3往下沉,值得注意的是,當遇到沉不下去的時候代表下面的泡泡比較重,所以就由下面的泡泡接替往下沉,如下圖框框中的情況。

  

  這樣下去再做幾輪之後就完成了,掌握了泡泡排序的核心想法之後,以下展示一些運用 Linked List 實作的泡泡排序。

ListNode* bubbleSortList(ListNode* head) {
    // bubble sort
    ListNode* tail = head;
    ListNode* tmp = head;
    ListNode* curr = head;
    ListNode* prev = head;
    
    // get Linked list size first
    int size = 0;
    while(tail) {
        tail = tail->next;
        size++;
    }
    
    for(int i=size;i>0;i--) {
        curr = head;
        prev = head;
        for(int j=0;j<i-1 && curr->next;j++) {
            // Compares two elements, and swaps if current is bigger than next
            if(curr->val > curr->next->val) {
                tmp = curr->next;
                curr->next = tmp->next;
                tmp->next = curr;
                // In linked list, swap has two case. In head or not.
                if(curr == head) {
                    head = tmp;
                    prev = tmp;
                } else {
                    prev->next = tmp;
                    prev = prev->next;
                }
            } else {
                curr = curr->next;
                if(j!=0) prev = prev->next;
            }
        }
    }
    
    return head;
}

  因為在 Linked List 中通常都需要考慮是不是 head,那如果不想要那麼麻煩有一個小技巧就是使用 Pseudo Node,下面也展示使用這種方法實作的泡泡排序,就可以不用管是不是在 head,最後要注意要把這個 Pseudo Node 還回去,不然會 Memory leak。

ListNode* bubbleSortList(ListNode* head) {
    // bubble sort with pseudo node
    ListNode* pseudo = new ListNode(0);
    pseudo->next = head;
    
    ListNode* curr = pseudo;
    ListNode* tmp = head;
    
    int size = 0;
    
    while(tmp) {
        tmp = tmp->next;
        size++;
    }
    
    for(int i=size;i>0;i--) {
        tmp = pseudo;
        for(int j=0;j<i-1 && tmp->next && tmp->next->next;j++) {
            // If we use pseudo node, we don't need to care head
            if(tmp->next->val > tmp->next->next->val) {
                ListNode* temp = tmp->next->next;
                tmp->next->next = temp->next;
                temp->next = tmp->next;
                tmp->next = temp;
            }
            tmp = tmp->next;
        }
    }

    tmp = pseudo;
    curr = pseudo->next;

    // Care memory leak
    delete tmp;

    return curr;
}

  最後就是,其實也未必要事先知道整個 Linked List 的 size 是多少,可以先設定一個 tail 初始是 NULL,只要發現下一個點是 tail 就知道這輪結束了,只要更改一下 tail 就可以進行下一輪,也是可以弄出來。

ListNode* bubbleSortList(ListNode* head) {
    // bubble sort with no size
    ListNode* tmp;
    ListNode* curr = head;
    ListNode* prev = head;
    ListNode* tail = NULL;
    
    while(head!=tail) {
        curr = head;
        prev = head;
        while(curr && curr->next && curr->next!=tail) {
            if(curr->val > curr->next->val) {
                tmp = curr->next;
                curr->next = tmp->next;
                tmp->next = curr;
                if(curr==head) {
                    prev = tmp;
                    head = tmp;
                } else {
                    prev->next = tmp;
                    prev = prev->next;
                }
            } else {
                if(curr!=head) {
                    prev = prev->next;
                }
                curr = curr->next;
            }
        }
        // In each iteration, we need to adjust tail. And we know curr->next = tail, so we let tail = curr here.  
        tail = curr;
    }
    return head;
}

Insertion Sort:

  Insertion Sort 的想法很簡單,想像在玩撲克牌的時候,除了最可憐的發牌者,其他人會在牌發到一半的時候就先拿起來整理手牌,這時候我們整理的方式就是把新發過來的牌按照大小插入手中。插入排序的想法也是這樣,假設我們已經有一串已經排序完成的,這時候新進來的就按照他應該的位子把它插進去即可。

  一開始假設第一個數字是已經排序好的,之後第二個數字往前插,這樣就有兩個位子是排序好的,再來第三個數字再去找適當的地方放進去,以此類推即可完成排序,如下圖。

  

  藍色是已經排序完成的地方,乍看之下很像插入排序很快,只要幾步就完成了。其實不然,因為在尋找要差在哪裡也是需要時間的,所以整體時間跟泡泡排序差不多快。

ListNode* insertionSortList(ListNode* head) {
    // insertion sort
    if(!head) return NULL;
    ListNode* temp = head;
    
    // get size first
    int size = 0;
    while(temp) {
        size++;
        temp = temp->next;
    }
    // curr is the next element of sorted list 
    ListNode* curr = head->next;
    // prev->next == curr
    ListNode* prev = head;
    // tail indicates the tail of sorted list
    ListNode* tail = head;

    for(int i=1;i<size;i++) {
        temp = head;
        prev = head;
        // find the location to be inserted
        for(int j=0;j<i && temp->val < curr->val;j++) {
            temp = temp->next;
            if(j!=0) prev = prev->next;
        }
        // insert
        if(temp==head) {
            tail->next = curr->next;
            curr->next = head;
            head = curr;
        } else if(temp==curr) {
            tail = tail->next;
        } else {
            prev->next = curr;
            tail->next = curr->next;
            curr->next = temp;
        }
        curr = tail->next;
    }
    
    return head;
}

Selection Sort:

  選擇排序的想法也很簡單,每次選擇最大或最小的元素排到正確的位子,不同於泡泡排序,他不會有一個往下沉或往上浮的情況,而是就是找最小或最大的拉過去,所以可以得到最小交換次數,對於要根據一個很大的表格的某一項排序是很好用的,原因是因為表格很大,造成交換的時間需要很久,選擇排序可以盡量減少交換次數。

  

  其中藍色是本次最小,而綠色則是已經排序完成的地方,每次都選一個最小去跟適當的位子交換,只要做適當的次數就可以完成排序。

ListNode* selectionSortList(ListNode* head) {
    // selection sort
    if(!head) return NULL;
    ListNode* temp = head;
    // tail indicates the tail of sorted list
    ListNode* tail = head;
    // get size first
    int size = 1;
    while(tail->next) {
        tail = tail->next;
        size++;
    }
    ListNode* curr = head;
    ListNode* prev = head;
    ListNode* min_Prev = head;
    ListNode* minNode = head;
    for(int i=size;i>0;i--) {
        curr = head;
        prev = head;
        min_Prev = head;
        minNode = head;
        // find the min
        for(int j=0;j<i;j++) {
            if(curr->val < minNode->val) {
                minNode = curr;
                min_Prev = prev;
            }
            curr = curr->next;
            if(j!=0) prev = prev->next;
        }
        // let tail->next = min, because tail is last min.
        if(minNode==head) {
            tail->next = head;
            head = head->next;
            tail = tail->next;
            tail->next = NULL;
        } else {
            tail->next = minNode;
            min_Prev->next = minNode->next;
            tail = tail->next;
            minNode->next = NULL;
        }
    }
    return head;
}

Merge Sort:

  Merge Sort 的想法比較複雜一點,先看圖在講解應該會比較有感覺。

  

  想像如果有兩個已經排序好的串列,要把他接成一個已經排序完成的應該不會太難。Merge Sort 的概念就是這樣,先把每一個元素當成一個已經排序好的,之後依序往上接,就能排序完成。

  實作上比起上面三種都還有容易理解,且因為複雜度為 O(nlogn) 所以速度比起上面三種快非常多,在 leetcode 上,運用 Merge Sort 大概只需要 20ms 可以跑完的情況,如果運用上面三種都需要到 200ms 左右。

ListNode* merge(ListNode* l1, ListNode* l2) {
    // merge with pseudo node
    ListNode* temp = new ListNode(0);
    ListNode* q = temp;
    
    while(l1 && l2) {
        if(l1->val < l2->val) {
            temp->next = l1;
            temp = temp->next;
            l1 = l1->next;
        } else {
            temp->next = l2;
            temp = temp->next;
            l2 = l2->next;
        }
    }
    
    if(l1) temp->next = l1;
    if(l2) temp->next = l2;
    
    ListNode* head = q->next;
    delete q;
    return head;
}

ListNode* mergeSortList(ListNode* head) {
    // merge sort
    if(!head || !head->next) return head;
    
    ListNode* fast = head->next;
    ListNode* slow = head;
    
    // split list
    while(fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    fast = slow->next;
    slow->next = NULL;
    
    // sort each list
    ListNode* l1 = mergeSortList(head);
    ListNode* l2 = mergeSortList(fast);
    
    // merge sorted l1 and sorted l2
    return merge(l1, l2);
}

  同樣也可以不要使用 Pseudo Node 實作,以下是使用遞迴的方式弄 merge。

ListNode* merge(ListNode* l1, ListNode* l2) {
    // merge with recursive
    if(!l2) return l1;
    if(!l1) return l2;
    
    if(l1->val<l2->val) {
        l1->next = merge(l1->next, l2);
        return l1;
    } else {
        l2->next = merge(l1, l2->next);
        return l2;
    }
}

ListNode* mergeSortList(ListNode* head) {
    // merge sort
    if(!head || !head->next) return head;
    
    ListNode* fast = head->next;
    ListNode* slow = head;
    
    // split list
    while(fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    fast = slow->next;
    slow->next = NULL;

    // sort each list
    ListNode* l1 = mergeSortList(head);
    ListNode* l2 = mergeSortList(fast);
    
    // merge sorted l1 and sorted l2
    return merge(l1, l2);
}

結語:

  有任何問題,或是 code 有更好寫法,歡迎提出。也感謝 leetcode 提供環境方便測試 code 的正確性。:D