归并排序基于分制思想(二分法),可以将事件复杂度降低到 nlog(n),在很多情况下都能用到!
归并排序的重点是合并函数的partation()的书写。本文结合几个简单的例子,分别引出数组和单向链表的partation函数的书写。
一:数组的归并排序
/****合并函数****/ void Merge(int A[],int left,int mid,int right) { int len = right -left +1; int *tmp = new int [len]; int index = 0; int i = left; //前一组数据的起始位置 int j = mid +1 ; //后一组数据的起始位置 while(i<=mid && j<=right) { tmp[index++] = A[i]<=A[j]? A[i++] : A[j++] ; //等号保证归并排序的稳定性 } while(i <= mid) { tmp[index++] = A[i++] ; } while( j<=right) { tmp[index++] = A[j++] ; } for (int k=0;k<len;k++) { A[left++] = tmp[k]; } } |
这里的merge函数是一个将数组合并的过程,其精髓是建立了一个缓存,红色标出的语句。
二:连续数组的最大和
三:单链表的归并排序
1. 归并排序O(nlogn);2. 快慢指针定位链表中间节点。
复杂度分析:
T(n) 拆分 n/2, 归并 n/2 ,一共是n/2 + n/2 = n
/ \ 以下依此类推:
T(n/2) T(n/2) 一共是 n/2*2 = n
/ \ / \
T(n/4) ……….. 一共是 n/4*4 = n
#include <iostream> using namespace std; struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next() {} }; ListNode* mergeSort(ListNode* left, ListNode* right) { //merge时,只改变节点的指向,无需改变任何一个节点的内部值。 if (left == NULL && right == NULL) return NULL; ListNode duyy(0); ListNode *temp = &duyy; while (left != NULL && right != NULL) { if (left->val > right->val) { temp->next = right; right = right->next; } else { temp->next = left; left = left->next; } temp = temp->next; } if (left != NULL) temp->next = left; if (right != NULL) temp->next = right; return duyy.next; } ListNode* sortList(ListNode* head) { if (head == NULL || head->next == NULL) return head; ListNode* fast = head->next; ListNode* slow = head; while (fast != NULL && fast->next != NULL) { //快慢指针定位链表中间节点 slow = slow->next; fast = fast->next->next; } ListNode *left = sortList(slow->next); slow->next = NULL; //将中间节点的下一个节点指向空,用于merge时表示链表的终点。 ListNode* right = sortList(head); return mergeSort(left, right); //**此处一定是使用sortList()返回的left和right指针**。 } int main() { int n; cin >> n; ListNode *head = new ListNode(0); ListNode *curr = head; for (int i = 0; i < n; i++) { int a; cin >> a; ListNode *temp = new ListNode(a); curr->next = temp; curr = temp; } head = sortList(head->next); while (head != NULL) { cout << head->val << " "; head = head->next; } return 0; } |
可以看出单链表找中间位置不用于数组找中间位置直接下标和的一半就好,单链表找中间位置必须快慢指针法。
ListNode *Find_middle(ListNode *head) { if (!head || !head->next) return head; //使用快,慢指针的方法:慢指针走一步,快指针走两步 ListNode *slow = head, *fast = head->next; while (fast !=NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next; } return slow; } |
重点是单链表的合并函数
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) { if (pHead1 == NULL) return pHead2; if (pHead2 == NULL) return pHead1; ListNode mergeNode(0); ListNode *pNew = &mergeNode; //遍历指针 while (pHead1 && pHead2) { if (pHead1->val <= pHead2->val) { pNew->next = pHead1; pHead1 = pHead1->next; } else { pNew->next = pHead2; pHead2 = pHead2->next; } pNew = pNew->next; } if (pHead1) pNew->next = pHead1; if (pHead2) pNew->next = pHead2; return mergeNode.next; } |
下面的递归调用比上面的清爽多了!
ListNode *sortList(ListNode *head) {
if (head == NULL || head->next == NULL) return head;
ListNode *pMid = Find_middle(head);
ListNode *pRightHead = pMid->next;
pMid->next = NULL;
ListNode *left = sortList(head);
ListNode *right = sortList(pRightHead);
return Merge(left, right);
}