单向链表的排序-插入、归并与快排

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013457167/article/details/82262206

好久没有做链表相关的题了,在八月的最后一天,实现单向链表的排序,以此纪念。
参考:牛客网
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函数,实现上述步骤。

不过这里需要注意的是:寻找链表的中点,当链表节点个数为偶数时,需要返回中值靠左的节点,比如当只有两个数时,返回第一个数位置的索引。目的是使分治结束。

代码:
  1. 快慢指针;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;
}

猜你喜欢

转载自blog.csdn.net/u013457167/article/details/82262206