鏈表排序(冒泡、選擇、插入、快排、歸併、希爾、堆排序)

這篇文章分析一下鏈表的各類排序方法。html

 

如下排序算法的正確性均可以在LeetCode的鏈表排序這一題檢測。本文用到的鏈表結構以下(排序算法都是傳入鏈表頭指針做爲參數,返回排序後的頭指針)node

struct ListNode {算法

  int val;數組

  ListNode *next;函數

  ListNode(int x) : val(x), next(NULL) {}ui

  };spa

 

插入排序(算法中是直接交換節點,時間複雜度O(n^2),空間複雜度O(1).net

class Solution {
public:
    ListNode *insertionSortList(ListNode *head) {
        // IMPORTANT: Please reset any member data you declared, as
        // the same Solution instance will be reused for each test case.
        if(head == NULL || head->next == NULL)return head;
        ListNode *p = head->next, *pstart = new ListNode(0), *pend = head;
        pstart->next = head; //爲了操做方便,添加一個頭結點
        while(p != NULL)
        {
            ListNode *tmp = pstart->next, *pre = pstart;
            while(tmp != p && p->val >= tmp->val) //找到插入位置
                {tmp = tmp->next; pre = pre->next;}
            if(tmp == p)pend = p;
            else
            {
                pend->next = p->next;
                p->next = tmp;
                pre->next = p;
            }
            p = pend->next;
        }
        head = pstart->next;
        delete pstart;
        return head;
    }
};

選擇排序(算法中只是交換節點的val值,時間複雜度O(n^2),空間複雜度O(1)指針

class Solution {
public:
    ListNode *selectSortList(ListNode *head) {
        // IMPORTANT: Please reset any member data you declared, as
        // the same Solution instance will be reused for each test case.
        //選擇排序
        if(head == NULL || head->next == NULL)return head;
        ListNode *pstart = new ListNode(0);
        pstart->next = head; //爲了操做方便,添加一個頭結點
        ListNode*sortedTail = pstart;//指向已排好序的部分的尾部
       
        while(sortedTail->next != NULL)
        {
            ListNode*minNode = sortedTail->next, *p = sortedTail->next->next;
            //尋找未排序部分的最小節點
            while(p != NULL)
            {
                if(p->val < minNode->val)
                    minNode = p;
                p = p->next;
            }
            swap(minNode->val, sortedTail->next->val);
            sortedTail = sortedTail->next;
        }
       
        head = pstart->next;
        delete pstart;
        return head;
    }
};

快速排序1(算法只交換節點的val值,平均時間複雜度O(nlogn),不考慮遞歸棧空間的話空間複雜度是O(1))code

這裏的partition咱們參考數組快排partition的第二種寫法(選取第一個元素做爲樞紐元的版本,由於鏈表選擇最後一元素須要遍歷一遍),具體能夠參考here

這裏咱們還須要注意的一點是數組的partition兩個參數分別表明數組的起始位置,兩邊都是閉區間,這樣在排序的主函數中:

void quicksort(vector<int>&arr, int low, int high)

{

  if(low < high)

  {

   int middle = mypartition(arr, low, high);

   quicksort(arr, low, middle-1);

   quicksort(arr, middle+1, high);

  }

}

對左邊子數組排序時,子數組右邊界是middle-1,若是鏈表也按這種兩邊都是閉區間的話,找到分割後樞紐元middle,找到middle-1還得再次遍歷數組,所以鏈表的partition採用前閉後開的區間(這樣排序主函數也須要前閉後開區間),這樣就能夠避免上述問題

class Solution {
public:
    ListNode *quickSortList(ListNode *head) {
        // IMPORTANT: Please reset any member data you declared, as
        // the same Solution instance will be reused for each test case.
        //鏈表快速排序
        if(head == NULL || head->next == NULL)return head;
        qsortList(head, NULL);
        return head;
    }
    void qsortList(ListNode*head, ListNode*tail)
    {
        //鏈表範圍是[low, high)
        if(head != tail && head->next != tail)
        {
            ListNode* mid = partitionList(head, tail);
            qsortList(head, mid);
            qsortList(mid->next, tail);
        }
    }
    ListNode* partitionList(ListNode*low, ListNode*high)
    {
        //鏈表範圍是[low, high)
        int key = low->val;
        ListNode* loc = low;
        for(ListNode*i = low->next; i != high; i = i->next)
            if(i->val < key)
            {
                loc = loc->next;
                swap(i->val, loc->val);
            }
        swap(loc->val, low->val);
        return loc;
    }
};

 

快速排序2(算法交換鏈表節點,平均時間複雜度O(nlogn),不考慮遞歸棧空間的話空間複雜度是O(1))

這裏的partition,咱們選取第一個節點做爲樞紐元,而後把小於樞紐的節點放到一個鏈中,把不小於樞紐的及節點放到另外一個鏈中,最後把兩條鏈以及樞紐鏈接成一條鏈。

這裏咱們須要注意的是,1.在對一條子鏈進行partition時,因爲節點的順序都打亂了,因此得保正從新組合成一條新鏈表時,要和該子鏈表的先後部分鏈接起來,所以咱們的partition傳入三個參數,除了子鏈表的範圍(也是前閉後開區間),還要傳入子鏈表頭結點的前驅;2.partition後鏈表的頭結點可能已經改變

class Solution {
public:
    ListNode *quickSortList(ListNode *head) {
        // IMPORTANT: Please reset any member data you declared, as
        // the same Solution instance will be reused for each test case.
        //鏈表快速排序
        if(head == NULL || head->next == NULL)return head;
        ListNode tmpHead(0); tmpHead.next = head;
        qsortList(&tmpHead, head, NULL);
        return tmpHead.next;
    }
    void qsortList(ListNode *headPre, ListNode*head, ListNode*tail)
    {
        //鏈表範圍是[low, high)
        if(head != tail && head->next != tail)
        {
            ListNode* mid = partitionList(headPre, head, tail);//注意這裏head可能再也不指向鏈表頭了
            qsortList(headPre, headPre->next, mid);
            qsortList(mid, mid->next, tail);
        }
    }
    ListNode* partitionList(ListNode* lowPre, ListNode* low, ListNode* high)
    {
        //鏈表範圍是[low, high)
        int key = low->val;
        ListNode node1(0), node2(0);//比key小的鏈的頭結點,比key大的鏈的頭結點
        ListNode* little = &node1, *big = &node2;
        for(ListNode*i = low->next; i != high; i = i->next)
            if(i->val < key)
            {
                little->next = i;
                little = i;
            }
            else
            {
                big->next = i;
                big = i;
            }
        big->next = high;//保證子鏈表[low,high)和後面的部分鏈接
        little->next = low;
        low->next = node2.next;
        lowPre->next = node1.next;//爲了保證子鏈表[low,high)和前面的部分鏈接
        return low;
    }
};

 


歸併排序(算法交換鏈表節點,時間複雜度O(nlogn),不考慮遞歸棧空間的話空間複雜度是O(1))                        本文地址

首先用快慢指針的方法找到鏈表中間節點,而後遞歸的對兩個子鏈表排序,把兩個排好序的子鏈表合併成一條有序的鏈表。歸併排序應該算是鏈表排序最佳的選擇了,保證了最好和最壞時間複雜度都是nlogn,並且它在數組排序中廣受詬病的空間複雜度在鏈表排序中也從O(n)降到了O(1)

class Solution {
public:
    ListNode *mergeSortList(ListNode *head) {
        // IMPORTANT: Please reset any member data you declared, as
        // the same Solution instance will be reused for each test case.
        //鏈表歸併排序
        if(head == NULL || head->next == NULL)return head;
        else
        {
            //快慢指針找到中間節點
            ListNode *fast = head,*slow = head;
            while(fast->next != NULL && fast->next->next != NULL)
            {
                fast = fast->next->next;
                slow = slow->next;
            }
            fast = slow;
            slow = slow->next;
            fast->next = NULL;
            fast = sortList(head);//前半段排序
            slow = sortList(slow);//後半段排序
            return merge(fast,slow);
        }
        
    }
    // merge two sorted list to one
    ListNode *merge(ListNode *head1, ListNode *head2)
    {
        if(head1 == NULL)return head2;
        if(head2 == NULL)return head1;
        ListNode *res , *p ;
        if(head1->val < head2->val)
            {res = head1; head1 = head1->next;}
        else{res = head2; head2 = head2->next;}
        p = res;
        
        while(head1 != NULL && head2 != NULL)
        {
            if(head1->val < head2->val)
            {
                p->next = head1;
                head1 = head1->next;
            }
            else
            {
                p->next = head2;
                head2 = head2->next;
            }
            p = p->next;
        }
        if(head1 != NULL)p->next = head1;
        else if(head2 != NULL)p->next = head2;
        return res;
    }
};

 


冒泡排序(算法交換鏈表節點val值,時間複雜度O(n^2),空間複雜度O(1))

class Solution {
public:
    ListNode *bubbleSortList(ListNode *head) {
        // IMPORTANT: Please reset any member data you declared, as
        // the same Solution instance will be reused for each test case.
        //鏈表快速排序
        if(head == NULL || head->next == NULL)return head;
        ListNode *p = NULL;
        bool isChange = true;
        while(p != head->next && isChange)
        {
            ListNode *q = head;
            isChange = false;//標誌當前這一輪中又沒有發生元素交換,若是沒有則表示數組已經有序
            for(; q->next && q->next != p; q = q->next)
            {
                if(q->val > q->next->val)
                {
                    swap(q->val, q->next->val);
                    isChange = true;
                }
            }
            p = q;
        }
        return head;
    }
};

 


對於希爾排序,由於排序過程當中常常涉及到arr[i+gap]操做,其中gap爲希爾排序的當前步長,這種操做不適合鏈表。

對於堆排序,通常是用數組來實現二叉堆,固然能夠用二叉樹來實現,可是這麼作太麻煩,還得花費額外的空間構建二叉樹

 

 

【版權聲明】轉載請註明出處:http://www.cnblogs.com/TenosDoIt/p/3666585.html

相關文章
相關標籤/搜索