好久没有做链表相关的题了,在八月的最后一天,实现单向链表的排序,以此纪念。
参考:牛客网
https://blog.csdn.net/bxw1992/article/details/77155152
https://www.cnblogs.com/TenosDoIt/p/3666585.html
一、单向链表的插入排序
题目:
Sort a linked list using insertion sort.
根据插入排序的定义,每次从当前链表和当前链表之前的数中找到当前节点值的正确位置(前驱),找到之后,将当前的节点使用尾插法插入,更新当前节点为原当前节点的下一个节点,重复上述步骤(查找位置、尾插),直到当前节点位置为尾节点为止。
代码:
ListNode *insertionSortList(ListNode *head) {
if( !head || !head->next) return head;
//ListNode *dumy = new ListNode(-1);
ListNode dummy(-1);
ListNode *pCur = head;
ListNode *pNew = &dummy;
while(pCur != NULL)
{
ListNode *pNext = pCur->next;
pNew = &dummy;//每次从头遍历,找到合适插入点的前驱
while(pNew->next !=NULL && pNew->next->val <pCur->val)
pNew = pNew->next;
//insert (尾插法要考虑next)
pCur->next = pNew->next;
pNew->next = pCur;
//update pCur
pCur = pNext;
}
return dummy.next;
}
二、单向链表的归并排序
题目:
Sort a linked list in O(n log n) time using constant space complexity.
分析:
因为题目要求复杂度为O(nlogn),故可以考虑归并排序的思想。
归并排序的一般步骤为:
1)将待排序数组(链表)取中点并一分为二;
2)递归地对左半部分进行归并排序;
3)递归地对右半部分进行归并排序;
4)将两个半部分进行合并(merge),得到结果。
所以对应此题目,可以划分为三个小问题:
1)找到链表中点 (快慢指针思路,快指针一次走两步,慢指针一次走一步,快指针在链表末尾时,慢指针恰好在链表中点);
2)写出merge函数,即如何合并链表。
3)写出mergesort函数,实现上述步骤。
不过这里需要注意的是:寻找链表的中点,当链表节点个数为偶数时,需要返回中值靠左的节点,比如当只有两个数时,返回第一个数位置的索引。目的是使分治结束。
代码:
- 快慢指针;2. 归并排序。
链表的归并排序空间复杂度是O(1)。
//找到链表中间位置(奇数中间那个,偶数偏左那个)
ListNode *Find_middle(ListNode *head)
{
if (!head || !head->next) return head;
//使用快,慢指针的方法:慢指针走一步,快指针走两步
ListNode *slow = head, *fast = head->next;
while (fast !=NULL && fast->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if (pHead1 == NULL) return pHead2;
if (pHead2 == NULL) return pHead1;
ListNode mergeNode(0);
ListNode *pNew = &mergeNode; //合并指针
while (pHead1 && pHead2)
{
if (pHead1->val <= pHead2->val)
{
pNew->next = pHead1;
pHead1 = pHead1->next;
}
else
{
pNew->next = pHead2;
pHead2 = pHead2->next;
}
pNew = pNew->next;
}
if (pHead1)
pNew->next = pHead1;
if (pHead2)
pNew->next = pHead2;
return mergeNode.next;
}
ListNode *sortList(ListNode *head) {
if (head == NULL || head->next == NULL) return head;
ListNode *pMid = Find_middle(head);
ListNode *pRightHead = pMid->next;
pMid->next = NULL;
ListNode *left = sortList(head);
ListNode *right = sortList(pRightHead);
return Merge(left, right);
}
三、单向链表的快速排序
题目:
Sort a linked list in O(nlogn) time using constant space complexity.
分析:
单链表的快速排序 时间复杂度O(nlogn),空间复杂度O(n)
快速排序的主要操作是用选取的枢轴作为切割的基准,左侧所有元素均小于枢轴,右侧均不小于枢轴。经典实现是从头和尾两个方向进行处理,由于单链表的移动方向是单向的,所以必须寻求其他方式。
用一个指针遍历链表,遇到小于枢轴的元素,就将其移到链表的开始处,剩下的就是不小于枢轴的元素;为了实现上述目标,建立两个指针,一个指针指向所有元素都小于枢轴的子链表,一个指针用于遍历。
代码:
//返回基准位置
ListNode *partitionList(ListNode* low, ListNode* high)
{
int pivot = low->val;
ListNode *pSlow = low;
ListNode *pFast = low->next;
//pSlow指向满足小于基准的最后一个元素
//pSlow到pFast之间是不满足条件的,用来交换使用
while (pFast != high)
{
if (pFast->val < pivot)
{
pSlow = pSlow->next;
swap(pSlow->val, pFast->val);
}
pFast = pFast->next;
}
swap(pSlow->val, low->val); //交换基准
return pSlow;
}
void quickSortCore(ListNode *low, ListNode *high) {
//递归终止条件
if (low == high) return;
ListNode *pivotIndex = partitionList(low, high);
//基准对应的元素并不处理
quickSortCore(low, pivotIndex);
quickSortCore(pivotIndex->next, high);
}
ListNode *sortList(ListNode *head) {
if (head == NULL || head->next == NULL) return head;
quickSortCore(head, NULL);
return head;
}