歸併排序連結串列的實現——學習筆記
阿新 • • 發佈:2019-01-07
歸併排序是效率非常高的一種排序方式,和快速排序一樣用了分治的思想。分治法的精髓在於將一個複雜問題分割成為多個簡單的子問題,然後將子問題逐個解決,最終合併在一起以後就是複雜問題的解了。
這篇文章主要講歸併排序連結串列的實現方式,如果想了解陣列的歸併排序,可移步這裡。
歸併排序的思想其實挺簡單的,簡而言之就是將數列分割成子序列,先對子序列進行排序,再將排好序的子序列合併成一個完整的數列。具體例子如下:
[3 7 6 4 8 9 2 1] / \ 分割 [3 7 6 4] [8 9 2 1] / \ / \ 分割 [3 7] [6 4] [8 9] [2 1] / \ / \ / \ / \ 分割 [3] [7] [6] [4] [8] [9] [2] [1] \ / \ / \ / \ / 合併 [3 7] [4 6] [8 9] [1 2] \ / \ / 合併 [3 4 6 7] [1 2 8 9] \ / 合併 [1 2 3 4 6 7 8 9]
分割時從數列中間開始,將數列分成兩部分,然後對分割後的序列繼續進行分割直到分割不能再分為止。然後對已經排好序的子序列進行合併,重新產生一個排序好的數列。
用連結串列進行歸併排序和陣列排序略有不同,但是思想還是一樣的。唯一的難點在於如何找到連結串列的中間點,這裡就要運用到了一個額外的技巧——快慢指標。快慢指標常常用於判斷連結串列中是否有環,簡單來說就是有兩個指標,快指標每次走兩個節點,慢指標每次走一個節點,當快指標走到連結串列盡頭時,慢指標才走到了連結串列的一半,此時就是我們想要的中間節點。
看程式碼:
//連結串列結構單位 struct Node{ int val; Node* next; }; //node -- 連結串列表頭 Node* MergeSort(Node* node){ //先判斷連結串列長度是否大於1,小於1時無須排序 if(node!=NULL&&node->next!=NULL){ //運用快慢指標,找到連結串列的中間節點 Node *fast=node->next; Node *slow=node; while(fast!=NULL&&fast->next!=NULL){ fast=fast->next->next; slow=slow->next; } //將連結串列分成兩部分進行分割 Node *p1=MergeSort(slow->next); slow->next=NULL; //這兒很重要,仔細想想為什麼 Node *p2=MergeSort(node); //對兩條子鏈進行歸併 Node *p0=(Node *)malloc(sizeof(Node)); Node *p=p0; while(p1!=NULL&&p2!=NULL){ if(p1->val<p2->val){ p->next=p1; p1=p1->next; }else{ p->next=p2; p2=p2->next; } p=p->next; } if(p1!=NULL){ p->next=p1; } if(p2!=NULL){ p->next=p2; } p=p0->next; free(p0); return p; } return node; }