Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
将k个链表排序,并求出时间复杂度。
方案一:
第一个和第二个合并,结果在和第三个合并……最后和第k个合并。本来以为时间复杂度为nk,其实不然。前两个合并后,耗费2n个时间。再和第三个合并,耗费3n个时间,最后和第k个合并,耗费kn个时间。总的时间为(2+3…+k)*n = n*k*k的时间复杂度。所有不可取。这种好实现,循环n次,然后两个两个合并即可。
方案二:
其实可以把k个链表看成k个点,对一个含有k个元素的数组,我们有快速排序、归并排序、堆排序等。但由于这道题目的应用环境,每个元素含有n个数据,快速排序不可能实现。那么考虑归并排序。其实在方案一当中,两两之间的合并已经是归并排序了。那么现在对整个k个链表应用归并排序。每次归并排序,nk个元素都要比较,时间复杂度为nk,一共需要logk次归并,所以时间复杂度为O(nklogk)。
有两种实现方法,递归和循环。其中循环的方法值得借鉴
//通用方法,这里不管有多少个链表
ListNode *MergeKLists(LinkList arr[],int l,int h)
{
if(l < r){
int m = (l+r)/2;
return Merge(MergeKLists(arr,l,m),MergeKLists(arr,m+1,h));
}
return arr[l];
}
//当能求得链表的个数的时候,可以利用循环代替递归来进行
ListNode *MergeKLists(LinkList arr[],int k)
{
LinkList first,second,result;
if(k < 1)
return NULL;
if(k == 1)
return arr[0];
while(k > 1){
int half = (k+1)/2; //这里必须加1,因为k代表的是个数,当循环结束后,会有k = half,当k为基数的时候,个数会减少
for(int i = 0;i + half < size;i++){
first = arr[i];
second = arr[i+half];
result = Merge(first,second);
arr[i] = result;
}
k = half;
}
return arr[0];
}
ListNode *Merge(LinkList l1,LinkList l2)
{
ListNode *dummy = (LinkList)malloc(sizeof(ListNode));
dummy->next = l1; //初始化,为后面求解方便
ListNode *cur = dummy; //父节点
while(l1 != NULL && l2 != NULL){
if(l1->data < l2->data)
l1 = l1->next;
else{
LinkList temp = l2->next;
l2->next = cur->next;
cur->next = l2;
l2 = temp;
}
cur = cur->next;
}
if(l2 != NULL)
cur->next = l2;
return dummy->next;
}
假设总共有k个list,每个list的最大长度是n,那么运行时间满足递推式T(k) = 2T(k/2)+O(n*k)。根据主定理,可以算出算法的总复杂度是O(nklogk)。如果不了解主定理的朋友,可以参见 主定理-维基百科 。空间复杂度的话是递归栈的大小O(logk)。
方案三:
我们还要尝试堆排序。我们可以先将k个链表的第一个元素进行堆排序,那么取出最小数据,并将最小数据所在链表的下一个元素放入堆中。因此一次取元素,时间复杂度为logk,一共有nk个元素。总的时间复杂度为O(nklogk)。空间复杂度是堆的大小,即为O(k)。
由于以往看到的堆排序都是在一个数组中进行,比较好写,但是这个题目的元素是一个结点,并且也不在数组中。所以可能的堆排序结点为(没有具体实现,只是考虑了一下):
typedef struct Node{
int data;
struct Node *next;
}ListNode,*LinkList;
typedef struct HeapNode{
ListNode *LNode;
struct HeapNode *left,*right;
}HeapNode;
但是在C++中好实现,已经封装好了堆的实现。代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class ListNodeCompare:public binary_function<ListNode*,ListNode*,bool>
{
public:
bool operator()(ListNode* t1,ListNode* t2)const
{
if ( !t1||!t2 )
return !t2;
return t1->val>t2->val;
}
};
class Solution {
public:
ListNode *mergeKLists(vector<ListNode *> &lists) {
// Note: The Solution object is instantiated only once and is reused by each test case.
if (lists.empty())
return NULL;
priority_queue<ListNode*,vector<ListNode*>,ListNodeCompare> Q;
for(int i=0;i<lists.size();i++)
if ( lists[i]!=NULL)
Q.push(lists[i]);
ListNode guard(-1);
ListNode* tail=&guard;
while(!Q.empty())
{
ListNode* toAdd=Q.top();
Q.pop();
tail->next=toAdd;
tail=tail->next;
if (toAdd->next)
Q.push(toAdd->next);
}
return guard.next;
}
};
推荐链接:PriorityQueue