# Linked list Bubblesort
contributed by <`Feng270`>
GitHub: [linkedlist_bubblesort](https://github.com/Feng270/linkedlist_bubblesort.git)
## 題目要求
此次補考題目第一題為給定一個 single linked list 的 node 定義如下:
```clike=c=
typedef struct __node {
int val;
struct __node *next;
} node;
```
寫出 sort 函式以不更動 val 值為限,對 linked list 做 bubblesort。
## 思路
這題乍看之下不難,但實際寫起來會發現很多的細節沒有注意到,例如一般在考慮 linked list 的時候通常不會想到會有 loop 的出現,而這裡的 loop 不單單只有環狀 linked list 更會有以下的情況產生,這時候一路到底的走訪方式會陷入無窮回圈...
![loop in linked list](http://www.geeksforgeeks.org/wp-content/uploads/2009/04/Linked-List-Loop.gif)[1]
有幾個問題如下:
1. 最基本的 linked list 複習
2. 克服 loop 的情況
3. 若修改 head 時 function 需要使用 pointer to pointer,以下分幾個部分來一一說明。
### Linked list loop 偵測
首先討論的是 loop 的偵測,這裡使用的是名為 `Floyd’s Cycle-Finding` 的演算法,其中概念很簡單,設兩個 pointer 名為 slow 以及 fast 兩個都從 head 開始走,slow 每次只走一步,而 fast 一次走兩步,假設有 loop 存在時 slow 終會與 fast 指到相同的節點。
以上圖為例,slow 與 fast 走訪節點如下表,得知在第 5 次迭代時會偵測到 loop 發生。
|迭代|slow|fast|
|-|-|-|
|1|1|1|
|2|2|3|
|3|3|5|
|4|4|3|
|5|5|5|
但有另一個問題出現,在 bubblesort 中會希望能夠知道整個 linked list 的長度使得程式撰寫較容易,而顯然以上的做法並沒有辦法知道什麼時候 linked list 會進入 loop,因而無法計算真正的長度,但真的是如此嗎?
今天我們假設 head 到 loop 起點的距離爲 `a`,而 loop 起點到相遇點的距離爲 `b`,相遇點回到 loop 起點的距離爲 `c`,因爲 fast 是 slow 的兩倍速度,因此 fast 走過的距離 = slow 走過距離的兩倍,slow 走的距離爲 `a + b`,fast則是 `a + b + c + b`可得以下推導:
$\quad 2slow = fast$
$\quad 2(a + b) = a + b + c +b$
$\quad a = c$
得證相遇時回到 loop 起點的距離是 head 到 loop 起點的距離,所以我們可以利用這個特性,找到 loop 起點,並分別計算 loop 內節點個數,再與 loop 外節點個數加總就是總長度了。
### Bubble Sort[2]
Bubble sort 可以簡單表示為下圖,每次迭代都會比較自己與下一個節點,以此圖來說大者會被 swap 到最後,直到所有節點被迭代過一次
![bubblesort](https://i.imgur.com/vUHj5dE.png)
### 更正 loop
Singal linked list 有個缺點是不知道上一個節點是誰,這時候我們在 swap 的時候會先將上個節點記下來,但是在 loop 的情況下(如上圖),第 2 個節點會有兩個上節點,若用一般的 swap 方法並不會記住 5 這個節點,只會記到 1 而這樣有一個問題,當節點 1 與節點 2 互相 swap 時,節點 5 的 next pointer 會被指到新的節點 1(也就是舊的節點 2),如下表示:
```
原先的 list
n1 -> n2 -> n3
| |
n5 <- n4
Swap 之後的 list
n2 -> n1 -> n3
| |
<---- n5 <- n4
```
要解決這個問題的話就必須先記住還沒 swap 前的 loop 起點,最後完成排序後再將最尾巴的 next pointer 指回正確的位置。
## 實作
有了上面的基礎,我們可以來實作 linked list 的 bubble sort,總共分成四個部分,分別是
* 產生一個 loop linked list
* 找到這個 linked list 的長度
* 進行 bubble sort
* 印出結果
以下將會一一介紹
### 產生一個 loop linked list
這裡使用了 C 的亂數產生器來產生一個 linked list,並手動指了最後節點的 next pointer 到任一其他節點
```clike=C=
// 先建立 head 節點
node *head = malloc(sizeof(node)),*now,*prev;
head->val=1;
int i;
srand(time(NULL));
// 產生額外10個節點
for (i=0;i<10;i++) {
now = malloc(sizeof(node));
now->val = rand()%100;
if (i==0) {
head->next = now;
prev = now;
}
else {
prev->next = now;
prev = now;
}
}
// Create a loop.
now->next=head->next->next->next;
```
### 找到這個 linked list 的長度
這裡可以分成兩個情況來討論,有 loop 以及沒有 loop 的情況,一開始便設有 slow 以及 fast 兩個 pointer,若一次走一步的 slow 與一次走兩步的 fast 相遇,則進入 loop 的運算,反之如果不存在 loop 那就只有 count++ 而已。
而 loop 中要怎麼計算長度呢?既然我們知道 slow 與 fast 相遇的節點一定在 loop 中,那麼只要一直走訪 next 節點,是必會回到原本 slow 與 fast 相遇的節點,也可得知 loop 內的長度了。
另外使用上述的方法,當 slow 與 fast 相遇時,讓 slow 從 head 開始,與 fast 都一次走一步,相遇的點即為 loop 的起點,計算 slow 走的長度為進入 loop 前的長度。
```clike=c=
int findlength(node *head) {
node *fast = head,*slow = head;
int count=0;
while(slow!=NULL) {
count++;
slow = slow->next;
if (fast->next!=NULL && fast->next->next!=NULL){
fast = fast->next->next;
// If loop existed.
if (slow==fast) {
node *tmp = slow;
int countloop = 1;
while (tmp->next!=slow) {
// counting nodes in loop
countloop++;
tmp = tmp->next;
}
slow = head;
count = 0;
while(slow!=fast) {
slow = slow->next;
fast = fast->next;
// counting nodes outside loop
count++;
}
count += countloop;
return count;
}
}
}
return count;
}
```
### 進行 bubble sort
有了長度之後就可以方便的進行排序了,底下有幾點要注意
* 由於會改到傳入的 head,因此必須定義成 pointer to pointer
* 首先為了防止 loop 的終點改變,必須先在排序前走訪過所有節點並記下 loop 的終點(此處為loopend)
* 中間 swap 的部分需要很清楚指標的使用,而且 head 節點需要特別處理
* 排序完之後檢查是否為 loop 是的話更正 next pointer
```clike=c=
void bubblesort(node **head,int length) {
int i,j;
node *now , *prev ,*loopend = NULL;
// 記錄最後一個節點指向的節點,若不是 NULL 即為 loop
for (i = 0,now = *head;i<length;i++,now = now->next)
loopend = now->next;
for (i = 0;i<length;i++) {
now = *head, prev = *head;
for (j = 0;j<length-1;j++) {
if (now->val > now->next->val) {
if (now == loopend)
loopend = now->next;
else if (now->next == loopend)
loopend = now;
// Swap node
node *tmp = now->next;
now->next = tmp->next;
tmp->next = now;
if (now == *head){
*head = tmp;
prev = tmp;
}
else {
prev->next = tmp;
prev = prev->next;
}
}
else {
now = now->next;
if (j!=0)
prev = prev->next;
}
}
}
// If loop existed, correct the next pointer.
if (now->next!=NULL)
now->next=loopend;
}
```
### 印出結果
用迴圈根據長度印出 linked list,此處使用長度以免陷入 loop
```clike=c=
void printresult(node *head,int length) {
node *now;
int i;
for (now=head,i=0;i<length-1;now=now->next,i++)
printf("%d,",now->val);
printf("%d\n",now->val);
}
```
## 結論
透過這次的補考,真的是在複習以前“曾經”學過的東西,可是我光不加 loop 的linked list 就寫的不太順了,不透過編譯器也沒辦法確保 code 不會出問題,透過這次的補考把該有的找回來,另外以前在寫 linked list 時也沒有想過會有 loop 的出現,還想說寫好 bubble sort 就可以交差,後期甚至花了許多時間想 loop 到底要怎麼偵測及處理。
## 參考資料
1. [Find length of loop in linked list - GeeksforGeeks](http://www.geeksforgeeks.org/find-length-of-loop-in-linked-list/)
2. [Linked List Sort - npes87184.github.io](https://npes87184.github.io/演算法/2015/09/12/linkedListSort.html)
3. [Linked List Cycle| LeetCode题解](https://siddontang.gitbooks.io/leetcode-solution/content/linked_list/linked_list_cycle.html)