1. 程式人生 > >單向連結串列的排序-插入、歸併與快排

單向連結串列的排序-插入、歸併與快排

好久沒有做連結串列相關的題了,在八月的最後一天,實現單向連結串列的排序,以此紀念。
參考:牛客網
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;
}