Circular Doubly linked list 實作 bubble sort
前言
本問題出自於 jserv - Linux 核心設計講座 第一周 linked list 和非連續記憶體操作 底下題目一的延伸題目

在這篇文章會紀錄關於 bubble sort 的實現。
node 結構
struct node {
    int data;
    struct node *next, *prev;
};
本題目 linked list 屬於 circular doubly linked list, 所以處理上還需要考慮循環的問題。
swap 之後要把 node 的 prev 跟 next pointer 處理好。
Bubble sort
void bubble_sort(int *arr, const int length) 
{
    for(int i = 0; i < length-1; i++) {
        int flag = 0;
        for(int j = 0; j < length-i-1; j++) {
            if(arr[j] > arr[j+1]) {
                int t = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = t;
                flag = 1;
            }
        }
        if(!flag) break;
    }
}
在寫 linked list 版本的 sort 之前,先來看一下一般 array 版本的實現方式。
基本上 bubble sort 算是一個容易理解的演算法,現在比較大的問題是要先實現一個 swap 來對兩個 node 交換。
swap_node
void swap_node(struct node *n1, struct node *n2) {
    n1->prev->next = n2;
    n2->prev = n1->prev;
    
    n1->next = n2->next;
    n2->next->prev = n1;
    
    n1->prev = n2;
    n2->next = n1;
}
因為我們 linked list 是採用 doubly linked list,所以還要額外處理 prev 指標。
以下用簡單的示意圖解釋一下這個 swap 是怎麼運作的

今天我們想要 swap node_2, node_3,經過 swap 之後應該要變成這樣:

先複習一下我們 swap function 的定義
swap_node(node_2, node_3);
所以我們傳入的是 node_2, node_3 的地址,node_1, node_4 則不能直接存取。
接著我們一步一步來解析,先處理 node_1, node_3 之間的連結
node_1->next = node_3;
node_3->prev = node_1;
因為我們沒辦法直接存取 node_1,所以要透過 node_2->prev 來表示
node_2->prev->next = node_3;
node_3->prev = node_2->prev;

接著處理 node_2, node_4 之間的連結
node_2->next = node_4;
node_4->prev = node_2;
因為我們無法直接存取 node_4, 所以要表示成 node_3->next;
node_2->next = node_3->next;
node_3->next->prev = node_2;
處理完之後變成這樣,最後只需要處理 node_2, node_3 之間的連結

讓 node_2->prev 指向 node_3, node_3->next 指向 node2,實現最後的交換。
node_2->prev = node_3;
node_3->next = node_2;
最後完成如下

建議如果對指標不熟悉的最好自己拿紙筆畫一次,會清楚很多。
Bubble sort with Circular doubly linked list
void bubble_sort(struct node **head, const int length) {
    if(!*head) return;
    struct node *tnode; // temp
    struct node *lnode = *head;
    struct node *rnode = lnode->next;
    for(int i = 0; i < length - 1; i++) {
        int flag = 0;
        for(int j = 0; j < length - i - 1; j++) {
            if(lnode->data > rnode->data) {
                swap_node(lnode, rnode);
                tnode = rnode;
                rnode = lnode;
                lnode = tnode;
                flag = 1;
            }
            lnode = lnode->next;
            rnode = rnode->next;
        }
        for(int j = 0; j < i+1; j++)
            lnode = lnode->next;
        rnode = lnode->next;
        if(!flag) break;
    }
    *head = lnode;
}
接著來介紹 bubble sort 在 circular doubly linked list 中的實現
根據剛剛 array 版本的 bubble sort,我們需要兩個指標指向要比較的兩點。
struct node *lnode;
struct node *rnode;
跟一般的 bubble sort 實現原理基本相同,只是因為我們的資料結構是 circular doubly linked list,所以有幾個需要注意的地方
if(lnode->data > rnode->data) {
    swap_node(lnode, rnode);
    tnode = rnode;
    rnode = lnode;
    lnode = tnode;
}
繼續用剛剛的例子,假設今天是 node_2, node_3 要交換
此時 lnode 指向 node_2, rnode 指向 node_3

交換完之後會變成

這時候為了後續的交換,需要把 lnode, rnode 指向正確的地方
才需要這段程式碼
tnode = rnode;
rnode = lnode;
lnode = tnode;

傳統的 bubble sort 每一輪比完都要從 arr[0] 開始重新比較,在 circular doubly linked list 中,我們每輪結束都要把 lnode 重新指向起點,並將 rnode 指向 lnode->next。
for(int j = 0; j < i+1; j++)
    lnode = lnode->next;
rnode = lnode->next;
完整的程式碼放在 GitHub
之後也會補上延伸題目的 merge sort。