题目:Sort List

对链表排序;

要求:时间复杂度O(nlogn),空间复杂度O(1)

考虑使用归并排序。

归并排序的思想:先将它划分成k各部分每个部分排好序后,再两两合并,知道所有的合并到一起,就排好序。

由于要求空间复杂度O(1),所以不能递归。

下面的实现是初始步长为2时的归并排序的情况。

ListNode* LeetCode::mergeList(ListNode* l1, ListNode* l2){
    if (!l1)return l2;
    if (!l2)return l1;
    ListNode* head = nullptr;
    ListNode* p = nullptr;
    if (l1->val <= l2->val){//比较两个链表头的值的大小
        head = l1;//第一个小
        p = head;
        l1 = l1->next;
    }
    else{//第二个小
        head = l2;
        p = head;
        l2 = l2->next;
    }
    while (l1 && l2){//合并两个链表
        if (l1->val <= l2->val){
            p->next = l1;
            l1 = l1->next;
        }
        else{
            p->next = l2;
            l2 = l2->next;
        }
        p = p->next;
    }
    if (l1){//第一个链表有剩余
        p->next = l1;
    }
    if (l2){//第二个链表有剩余
        p->next = l2;
    }
    return head;
}

ListNode* LeetCode::sortList(ListNode* head){
    if (!head || !head->next)return head;
    int step = 2,length = 1;//step为步长,length为链表长度
    ListNode* p = head->next, *tail = nullptr;
    //因为后面从head->next开始合并所以这里也要对应,p从head->next开始
    while (p){//每两个节点排一次序
        if (!p->next){//长度为奇数
            ++length;
            break;
        }
        if (p->val > p->next->val){//替换两个相邻节点的值
            auto temp = p->val;
            p->val = p->next->val;
            p->next->val = temp;
        }
        p = p->next->next;//移动两步
        length += 2;
    }
    for (; step < length; step = step << 1)
    {
        p = head->next;//从第二个节点开始合并链表,不用判断头结点的特殊情况
        //用于合并的两个子链表
        ListNode* left = nullptr,* right = nullptr;
        tail = head;
        int cur = 1;
        while (cur + step < length){//当前位置之后没有step个节点
            left = p;
            for (int i = 1; i < step; i++){//找到第一个步长为step的子链表
                p = p->next;
            }
            cur += step;//更新当前位置
            right = p->next;
            p->next = nullptr;//第一个子链表的尾部置为空
            p = right;//更新当前位置指针
            if (cur + step < length){//是否有足够step个节点做第二个子链表
                for (int i = 1; i < step; i++){////找到第二个步长为step的子链表
                    p = p->next;
                }
                ListNode* q = p->next;
                p->next = nullptr;//第二个子链表的尾部置为空
                p = q;//更新当前位置指针
                left = mergeList(left, right);
            }
            else{//不够
                left = mergeList(left, right);//直接合并
                p = nullptr;
            }
            tail->next = left;//连接合并后的子链表
            while (tail->next)tail = tail->next;//找到新的尾巴
            tail->next = p;
            cur += step;//更新当前位置
        }
    }
    if (p && tail && tail != head){//尾部不够一个步长的子链表未合并
        tail->next = nullptr;
        p = mergeList(head->next, p);//保证head->next到tail的链表不为空
    }
    p = head->next;
    head->next = nullptr;
    head = mergeList(head, p);
    return head;
}

其实对链表排序,当数据量不多时,直接插入排序效率是最好的,因此,经常将直接插入排序和归并排序结合起来。

数据量少时,就直接使用直接插入排序;数据量多时,先拆分到数据量较小时,使用直接插入排序,然后再归并,使其整体有序。

下面的实现可以是任意步长,其中会先用直接插入排序来对初始的步长值的子链表排序。

ListNode* LeetCode::sortList(ListNode* head){
    if (!head || !head->next)return head;
    int step = 3,length = 1;//step为步长,length为链表长度
    ListNode* p = head;
    while (p->next){
        ListNode* pa = p;//记录需要插入排序的子链表的头结点的父节点
        ListNode* gson = nullptr;//记录需要插入排序的子链表的尾结点的子节点
        for (int i = 0; p->next && i < step; i++){//长度为步长的子链表
            p = p->next;
            ++length;
        }
        if (p->next){//未到链表尾部
            gson = p->next;//切断子链表尾部与原链表的联系
            p->next = nullptr;
        }
        pa->next = insertionSortList(pa->next);//插入排序
        p = pa;
        while (p->next)p = p->next;//找到子链表的尾部
        if (gson){//将切断的子链表与原链表的联系重新连接起来
            p->next = gson;
        }
    }
    ListNode* tail = nullptr;
    for (; step < length; step = step << 1)
    {
        p = head->next;//从第二个节点开始合并链表,不用判断头结点的特殊情况
        //用于合并的两个子链表
        ListNode* left = nullptr,* right = nullptr;
        tail = head;
        int cur = 1;
        while (cur + step < length){//当前位置之后没有step个节点
            left = p;
            for (int i = 1; i < step; i++){//找到第一个步长为step的子链表
                p = p->next;
            }
            cur += step;//更新当前位置
            right = p->next;
            p->next = nullptr;//第一个子链表的尾部置为空
            p = right;//更新当前位置指针
            if (cur + step < length){//是否有足够step个节点做第二个子链表
                for (int i = 1; i < step; i++){////找到第二个步长为step的子链表
                    p = p->next;
                }
                ListNode* q = p->next;
                p->next = nullptr;//第二个子链表的尾部置为空
                p = q;//更新当前位置指针
                left = mergeList(left, right);
            }
            else{//不够
                left = mergeList(left, right);//直接合并
                p = nullptr;
            }
            tail->next = left;//连接合并后的子链表
            while (tail->next)tail = tail->next;//找到新的尾巴
            tail->next = p;
            cur += step;//更新当前位置
        }
    }
    if (p && tail && tail != head){//尾部不够一个步长的子链表未合并
        tail->next = nullptr;
        p = mergeList(head->next, p);//保证head->next到tail的链表不为空
    }
    p = head->next;
    head->next = nullptr;
    head = mergeList(head, p);
    return head;
}