题目: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; }