昨天写了个链表的快速排序算法,刚好leetcode上面也有这道题,于是乎提交,发现测试用例中有一个很大的测试用例出现了Limited time exceeded错误,于是乎发现解法不对,毕竟快排是一个平均时间复杂度O(nlogn),但是最坏情况下O(n^2)的排序算法,看到网上有人说应该使用归并排序,其时间复杂度为O(nlogn),一下子反应不过来,故先复习下归并排序。
其实归并排序也是属于分治的方法(divide and conquer),其主要思想是对于若干个有序的数组,将其合并成一个,这样合并的算法复杂度为O(n),然后根据你将数据分成几份,这样算分割的复杂度,比如典型的二路归并排序,其划分的时间复杂度为O(logn),这样算法整体的复杂度即为O(nlogn).以下方法都以二路归并为例。
对于归并排序,我觉得主要的步骤有两个:1.划分 2.合并,这其实也是所有分治算法的步骤。
【对于数组使用归并排序】
划分如何划呢,两路归并的话,我们最小的合并单元即为2个元素合并,那么我们就可以采用递归的方法将数组不断的分为两半,直到这一部分数据只剩下一个,这样就能将两个大小都为1的子数组合并,在我查找资料的时候我发现百度百科中对归并算法介绍中的一幅图非常形象(原文地址):
图中我们可以看到,对于原数组,我们不断将数据分开,直到都变为单独的一个数据,然后开始进行合并。
这样的话,其实用代码来表达就非常容易了,在归并的过程中,由于需要有一个中间数组存储归并结果,因此我们在算法开始就申请一个内存供存储中间结果使用,以免在递归过程中频繁申请释放内存导致效率低下。
下面将我写的完整代码放在下面:
void mergeArray(int A[],int low,int mid, int high,int temp[]){
int i = low;
int j = mid+1;
int k = low;
while(i<= mid && j <= high){
if(A[i] < A[j])
temp[k++] = A[i++];
else
temp[k++] = A[j++];
}
while( i<= mid)
temp[k++] = A[i++];
while( j <= high)
temp[k++] = A[j++];
for( i = low; i<=high; i++)
A[i] = temp[i];
}
void mergeSort(int A[], int low ,int high,int temp[]){
if(low < high){
int mid = low + (high-low)/2;
mergeSort(A,low,mid,temp);
mergeSort(A,mid+1,high,temp);
mergeArray(A,low,mid,high,temp);
}
}
int main(){
int n = 10;
int *a = new int[n];
int *temp = new int[n];
for(int i = 0; i< n; i++)
a[i] = rand() % 100;
mergeSort(a,0,n-1,temp);
for(i = 0; i< n; i++)
cout<<temp[i]<<" ";
cout<<endl;
delete a;
delete temp;
return 0;
}
【链表归并排序】
对于链表的归并排序,由于链表不像数组那样能够根据下标进行随机,所以链表的分割我们于是就想到了跟那个判断链表有环还是无环的方法,设置两个指针,一个一次走一步,一个一次走两步,这样就能在n/2的时间内将链表平均分成两半,就能将问题规模不断缩小,最终得到结果,下面将代码附上:
#include <iostream>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int x):val(x),next(NULL){}
};
ListNode* mergeList(ListNode* left, ListNode* right){
ListNode* head = NULL;
ListNode* p = NULL;
ListNode* r_head = NULL;
if(left == NULL)
return right;
if( right == NULL)
return left;
if(left->val < right->val){
r_head = left;
left = left->next;
}
else{
r_head = right;
right = right->next;
}
r_head->next = mergeList(left,right);//这种合并方法非常巧妙,
//避免了使用非递归合并过程中必须对头指针进行引用从而改变值的麻烦。
return r_head;
}
ListNode* mergeSort(ListNode* head){
if(head == NULL || head->next == NULL)//don't need to sort
return head;
ListNode* slow = head;
ListNode* fast = head;
while(fast->next != NULL && fast->next->next !=NULL){
slow = slow->next;
fast = fast->next->next;
}
//split the linklist into two linklist
fast = slow->next;
slow->next = NULL;
slow = head;
return mergeList(mergeSort(slow),mergeSort(fast));
}
void printList(ListNode* head){
while(head!=NULL){
cout<<head->val<<" ";
head = head->next;
}
cout<<endl;
}
int main(){
int a[] = {1,4,3,2,1} ;
ListNode* head = new ListNode(a[0]);
ListNode* p =head;
for(int i = 1; i < 5; i++){
p->next = new ListNode(a[i]);
p = p->next;
}
printList(head);
ListNode* new_head = mergeSort(head);
printList(new_head);
return 0;
}