您的位置:首页 > 其它

LeetCode(148)Sort List

2014-02-04 08:09 399 查看
题目如下:

Sort a linked list in O(n log n) time using constant space complexity.

分析如下:

挺有意思的一道题目。表面上看,能够有O(n lgn)时间复杂度的算法为,快速排序,堆排序,归并排序,三者的空间复杂度分别为O(1), O(N),O(N)

所以一开始,我想着用快速排序的方法来解决,但是发现代码很难写出来。于是网上看了一下提示,发现其实方法选错了。应该使用的方法是归并排序。

通常而言,也就是针对数组而言,归并排序的空间复杂度为O(N), 你需要开出O(N)的额外空间来容纳数组,来表示归并后的顺序。但是,对于链表而言,你可以省下这部分空间的开销,你只需要改变节点的next指针的指向,就可以表示新的归并后的顺序了,所以空间复杂度陡然降到了O(1)。

我的代码:

244ms过大集合
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
 
class Solution {
public:
        ListNode* merge_list(ListNode* l_head,ListNode* r_head){
        ListNode* merged_head=NULL;
        ListNode* merged_tail=NULL;
        while(l_head!=NULL&&r_head!=NULL){
            if(l_head->val<=r_head->val){
                if(merged_head==NULL){
                    merged_head=l_head;
                    merged_tail=l_head;
                }else{
                    merged_tail->next=l_head;
                    merged_tail=merged_tail->next;
                }
                l_head=l_head->next;
            }else{
                if(merged_head==NULL){
                    merged_head=r_head;
                    merged_tail=r_head;
                }else{
                    merged_tail->next=r_head;
                    merged_tail=merged_tail->next;
                }
                r_head=r_head->next;
            }
        }
        
        if(l_head!=NULL)
            merged_tail->next=l_head;
        else if(r_head!=NULL)
            merged_tail->next=r_head;
        ListNode* p=merged_head;
        while(p!=NULL){
            p=p->next;
        }
        return merged_head;
    }
    
    ListNode* sort(ListNode* head, ListNode* tail){
        if(head==NULL||head==tail){
            head->next=NULL;//bug1 忘记写这句了,和数组不一样,这里是链表。每个新形成的小链表都要在结尾处用NULL断开。
            return head;
        }
        ListNode* fast=head;
        ListNode* slow=head;
        ListNode* pre;
        while(fast!=tail->next&&fast!=tail)//bug2  之前错写为了while(fast!=NULL&&fast->next!=NULL)
        {
            fast=fast->next->next;
            pre=slow;
            slow=slow->next;
        }
        ListNode* l_head=sort(head,pre);
        ListNode* r_head=sort(slow,tail);
        ListNode* merged_head=merge_list(l_head,r_head);
        ListNode* p=merged_head;
        while(p!=NULL){
            p=p->next;
        }
        return merged_head;
    }
    
    ListNode *sortList(ListNode *head) {
        if(head==NULL||head->next==NULL)
            return head;
        ListNode* pre=NULL;
        ListNode* cur=head;
        while(cur!=NULL){
            pre=cur;
            cur=cur->next;
        }
        return sort(head,pre);
    }
};


update: 2015-01-03

class Solution {
public:
    ListNode *sortList(ListNode *head) {
        if (head == NULL || head->next == NULL) return head;
        ListNode* slower = head; //slower是左1/2链表的尾巴
        ListNode* slow = head->next;//slow是右1/2链表的开始
        ListNode* fast = head->next->next;//fast每次走2步,slow每次走1步,当fast走到原链表结尾的时候,slow就走到了1/2点了。
        while(fast != NULL) {
            slower = slow;
            fast = fast->next;
            slow = slow->next;
            if(fast != NULL) fast = fast->next;
        }
//        slower = slow; //测试后发现,如果把slow或者slower向右移动一位,可能会造成infinite loop,所以还是应该保持slow就在原链表1/2处的位置。
//        slow = slow->next; //如果使用这个错误版本的代码,<span style="font-family: Arial, Helvetica, sans-serif;">{4,3,8,7,2,1,9,5,6,0} 这个例子会有infinite loop。</span>
        slower->next = NULL;
        ListNode* left = sortList(head);
        ListNode* right = sortList(slow);
        ListNode* dummy_head = new ListNode(-1); //避免讨论head是不是空。
        ListNode* tail = dummy_head;
        while(left!= NULL && right != NULL) {
            if (left->val < right->val) {
                tail->next = left;
                left = left->next;
            } else {
                tail->next = right;
                right = right->next;
            }
            tail = tail->next;
            tail->next = NULL;
        }
        while (left != NULL) {
            tail->next = left;
            left = left->next;
            tail = tail->next;
        }
        while (right != NULL) {
            tail->next = right;
            right = right->next;
            tail = tail->next;
        }
        
        ListNode* return_head = dummy_head->next;
        delete dummy_head;
        dummy_head = NULL;
        return return_head;
    }
};


小结扩展:

(1)挺有意思的一道题目,需要在新的条件和环境下去思考旧的结论是否依然成立。

(2)快速排序和归并排序的时间复杂度都是O(N lgN),但是CLRS说了,实践证明快速排序的速度比归并排序的速度更快,为什么呢?另外其实这个结论是有限制范围的,当对数组进行排序的时候,这个结论适用。为什么对于链表,却是归并排序的速度优于快速排序呢?这里看到的一段对比说得挺好,直接抄过来。

One of the main sources of efficiency in quicksort is locality of reference, where the computer hardware is optimized so that accessing memory locations that are near one another tends to be faster than accessing memory locations scattered throughout memory.
The partitioning step in quicksort typically has excellent locality, since it accesses consecutive array elements near the front and the back. As a result, quicksort tends to perform much better than other sorting algorithms like heapsort even though it often
does roughly the same number of comparisons and swaps, since in the case of heapsort the accesses are more scattered.

Additionally, quicksort is typically much faster than other sorting algorithms because it operates in-place, without needing to create any auxiliary arrays to hold temporary values. Compared to something like merge sort, this can be a huge advantage because
the time required to allocate and deallocate the auxiliary arrays can be noticeable. Operating in-place also improves quicksort's locality.

When working with linked lists, neither of these advantages necessarily applies. Because linked list cells are often scattered throughout memory, there is no locality bonus to accessing adjacent linked list cells. Consequently, one of quicksort's huge performance
advantages is eaten up. Similarly, the benefits of working in-place no longer apply, since merge sort's linked list algorithm doesn't need any extra auxiliary storage space.

That said, quicksort is still very fast on linked lists. Merge sort just tends to be faster because it more evenly splits the lists in half and does less work per iteration to do a merge than to do the partitioning step.

归纳一下,就是说,如果待排序的元素存储在数组中,那么快速排序相对归并排序就有两个原因更快。一是,可以很快地进行元素的读取(相对于链表,数组的元素是顺序摆放的,而链表的元素是随机摆放的),数组的partion这步就比链表的partion这步快。二是,归并排序在merge阶段需要辅助数组,需要申请O(N)的空间,申请空间也是需要时间的。而快排不需要额外申请空间。如果待排序的元素存储在链表中,快排的优点就变成了缺点。归并排序于是就速度更优了。
参考资料:

(1) http://stackoverflow.com/questions/7629904/why-is-mergesort-better-for-linked-lists
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: