# Week2 linked list bubble sort contributed by <`ian910297`> ## 問題描述 對一個 Singly linked list 做 bubble sort * 不能改變 value * 只能改變 next 的指標 Data Structure 如下 ```clike typedef struct __node { int val; struct __node *next; } Node; ``` ## bubble sort - 無考慮 cycle 一開始,我以為只需要針對頭尾特別討論,以下為程式碼 ```clike= void sort(Node **HEAD) { Node *cur, *tmp, *prev; Node *TAIL = NULL; While(*HEAD!=NULL && (*HEAD)->next!=TAIL) { cur = *HEAD; while(cur->next!=TAIL) { if(cur->val > cur->next->val) { // HEAD if(cur==*HEAD) { *HEAD = cur->next; } else { prev->next = cur->next; } // swap tmp = cur->next; cur->next = tmp->next; tmp->next = cur; // whether is TAIL or not if(cur->next != TAIL) { TAIL = cur; break; } // next loop prev = tmp; } else { // whether is TAIL or not if(cur->next->next == TAIL) { TAIL = cur->next; break; } // next loop prev = cur; cur = cur->next; } } } } ``` ## Cycle Finding 在老師提醒下,發現 singly linked list 仍可能會有 cycle 產生,也就是 circular linked list ,我使用[龜兔賽跑(Floyd's algorithm)](http://www.csie.ntnu.edu.tw/~u91029/Function.html#4)來偵測是否有 cycle 產生 有三種狀態需要做討論 ![](https://i.imgur.com/WEPT0Xx.jpg) 圖(1) cycle 在中間、圖(2) 頭尾相連、圖(3)為尾尾相連 * $a$ 為起始點 * $b$ 為連接點 * $c$ 為龜兔相遇位置 我們需要求得三點位置,才能進行處理 假設 $\overline{ac}$ 距離為 $X$ ,這代表 tortoise 走了 $X$ 步,那麼 hare 走了 $2X$ 步, $X$ 數值為多少並不重要,他只代表要花多少時間兩點才會相遇,不影響求出 $\mu , \lambda$ 接下來要分成三個步驟來處理 * step1: tortoise 速度為每次一步,hare 為每次兩步,兩者同時從起點 $a$ 出發,相遇時可以得到點 $c$。如果是圖(2)的狀況,在第一步結束就求完三點了 * step2: 兩點分別從點 $a$ 、 $c$ 出發,速度皆為一次一步,相遇時可以得到點 $b$。因為 $\overline{ac}$ 長度為 $X$,那麼 $cycle$ $c$ 長度也為 $X$,相遇在點 $b$ 時,所走的距離剛好都是 $X - \overline{bc}$ * step3: 從點 $b$ 出發,速度為一次一步,再次回到點 $b$ 可以得到 cycle 的長度 ## cycle finding 如果只需要判斷是否為 circular linked list,那麼只要執行 step1 的部分,是很簡單、快速。 除了計算 $\mu , \lambda$ ,還需要記錄整個串列的長度,若不記錄,會影響到之後 bubble sort 的實作。 ```clike Node* move(Node *cur) { if(cur!=NULL) { retunr cur->next; } else { return NULL; } } bool cycle_finding(Node *HEAD, Node **TAIL, int *length, int *mu, int *lambda) { // lambda is length // mu is the meet node's index Node *tortoise = HEAD; Node *hare = HEAD; // get meet point tortoise = move(tortoise); hare = move(move(hare)); while(hare!=NULL && tortoise!=hare) { tortoise = move(tortoise); hare = move(move(hare)); } // not loop if(hare==NULL) { *TAIL = NULL; *length = 0; tortoise = HEAD; while(tortoise!=NULL && (tortoise=move(tortoise))) { (*length)++; } return false; } // get mu *mu = 0; tortoise = HEAD; while(tortoise!=hare) { (*mu)++; tortoise = tortoise->next; hare = hare->next; } // get lambda *lambda = 1; tortoise = move(tortoise); *TAIL = tortoise; while(tortoise!=hare) { *TAIL = tortoise; (*lambda)++; tortoise = move(tortoise); } *length = *mu + *lambda; return true; } ``` ## bubble sort - 加入 cycle_finding 原本以為只要完成 cycle_finding ,得到 $\mu , \lambda$ 之後就離完成作業不遠,但實際實作之後,才發現要考慮的狀況比預想的複雜。 以下為處理 circular linked list 遇到的難題 * 要討論的狀況很多,只要交換牽扯到 $\mu$ ,都需要特別再將尾巴的鏈結重新指向 * 當交換發生在結尾時 * $\mu$ 是 $HEAD$ * $\mu$ 是 $TAIL$ * 當交換發生在頭時 * $\mu$ 是 $1$ * 一般交換 * $cur \to next$ 是 $\mu$ * $cur$ 是 $\mu$ * 不知如何判定是否到結尾,若採用之前的使用 pointer 當作結尾的方法來判定,點 $\mu$ 也代表 $TAIL$ ,若使用 pointer 就無法判定是否是第一次經過點 $mu$,還是已經到結尾 ```clike void sort(Node **HEAD) { Node *cur, *prev, *TAIL; int i, j; int length = 0; int mu = -1; int lambda = -1; bool has_cycle = cycle_finding(*HEAD, &TAIL, &length, &mu, &lambda); for(i=0; i<length-1; i++) { cur = *HEAD; prev = *HEAD; for(j=0; j<length-i-1; j++) { if(cur->val > cur->next->val) { if(cur->next==TAIL) {// for cycle if(mu==0) {// link to HEAD prev->next = cur->next; cur->next = *HEAD; } else if(cur->next==cur->next->next) {// link to self prev->next = cur->next; cur->next = cur; } else { prev->next = cur->next; cur->next = prev->next->next; } prev->next->next = cur; TAIL = cur; prev = prev->next; } else if(cur==*HEAD) { *HEAD = cur->next; if(mu==1) { cur->next = (*HEAD)->next; (*HEAD)->next = cur; TAIL->next = cur; } else { cur->next = (*HEAD)->next; (*HEAD)->next = cur; } prev = *HEAD; } else { prev->next = cur->next; cur->next = prev->next->next; prev->next->next = cur; if(j+1==mu) { TAIL->next = cur; } else if(j==mu) { TAIL->next = prev->next; } prev = prev->next; } } else { prev = cur; cur = cur->next; } } } } ``` ## 最終優化 仍使用 length 計算結尾,且若是 circular linked list ,我們只需在排序完成時,重新把 $TAIL$ 指向點 $mu$ 即可 ```clike void sort(Node **HEAD) { Node *cur, *prev, *TAIL; int i, j; int length = 0; int mu = -1; int lambda = -1; bool has_cycle = cycle_finding(*HEAD, &TAIL, &length, &mu, &lambda); for(i=0; i<length-1; i++) { cur = *HEAD; for(j=0; j<length-i-1; j++) { if(cur->val > cur->next->val) { if(cur==*HEAD) { *HEAD = cur->next; cur->next = (*HEAD)->next; (*HEAD)->next = cur; } else { prev->next = cur->next; cur->next = prev->next->next; prev->next->next = cur; prev = prev->next; } } else { prev = cur; cur = cur->next; } if(j+1==length-1) { TAIL = cur; } } } if(has_cycle) { i = 0; cur = *HEAD; while(i++<mu) { cur = cur->next; } TAIL->next = cur; } } ``` ## 問題討論 ### 以數學證明 Floyd's Algorithm :::info ::: ### ## 測試 目前還沒有完成測試的程式,先以FATESAIKOU同學提供的Utils產生測資作初步的驗證 ## 心得 原本在補考寫的程式碼,回家實做之後發現是完全錯誤,少了編譯器輔助,我發現我根本不能保證我程式碼的可執行性。