算法設計與分析(六

Merge k Sorted Lists

題目

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.

Example:

Input:
[
1->4->5,
1->3->4,
2->6
]
Output: 1->1->2->3->4->4->5->6

題目分析

題目很短,需要我們將k個已經有序的數組合併成一個有序數組,這些數組是用鏈表的方式表示的。很明顯,這是歸併排序和分治算法結合的題目。

基礎知識

  • 歸併排序
    歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。 將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。一般我們接觸的歸併排序是二路歸併排序,具體的過程如下:
    在這裏插入圖片描述
    這個過程不斷將一個數組分割,分割到兩個或者一個元素,然後又將有序的小的數組兩兩合併成一個有序的數組。這個排序算法是穩定的。

解答及算法步驟

這裏產生了兩種基本的想法,由於最後要合成一個數組,假設有A,B,C,D,E,F,G,H六個有序的數組

  • 第一種想法
    先合併A和B爲A,A和C合併結果爲A,A合併D作爲A,再合併E爲A,再合併F爲A,再合併G爲A,再合併H爲A,最後A即爲結果。

    • 算法步驟
      1. 新建一個鏈表存儲答案。
      2. 合併vector的前兩個鏈表,並將合併的鏈表刪除,將結果存儲回vector首部。
      3. 繼續2的操作,知道vector只有一個元素。
  • 第二種想法
    這可以說是對第一種想法的改進,時間複雜度更低,先合併A和B爲A1,再合併C和D爲A2,再合併E和F爲A3,再合併G和H爲A4,再合併A1和A2爲B1,再合併A3和A4爲B2,最後合併B1和B2。這剛剛好是歸併排序的逆過程。

    • 算法步驟
      1. 將vector分成兩個部分。
      2. 將這兩個部分的鏈表兩兩合併。
      3. 遞歸過2,知道只有一個元素。

源碼

  • 做法一
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0) {
            return NULL;
        }
        //every two merge
        while(lists.size() > 1) {
        	ListNode *temp = merge(lists[0], lists[1]);
            lists.erase(lists.begin());
        	lists.erase(lists.begin());
        	lists.insert(lists.begin(),temp);
        	
        }
        return lists[0];
    }
    ListNode* merge(ListNode *l1, ListNode *l2) {
    	if(l1 == NULL) {
    		return l2;
    	}
    	if(l2 == NULL) {
    		return l1;
    	}
    	if(l1->val < l2->val) {
    		l1->next = merge(l1->next, l2);
    		return l1;
    	} else {
    		l2->next = merge(l2->next, l1);
    		return l2;
    	}
    }
};
  • 做法二
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        //every two merge
        if(lists.empty()) {
            return NULL;
        }
        return merge(lists, 0, lists.size()-1);
    }


    ListNode* merge_two(ListNode *l1, ListNode *l2) {
    	if(l1 == NULL&&l2 == NULL) {
    		return NULL;
    	}
    	if(l1 == NULL) {
    		return l2;
    	}
    	if(l2 == NULL) {
    		return l1;
    	}
    	if(l1->val < l2->val) {
    		l1->next = merge_two(l1->next, l2);
    		return l1;
    	} else {
    		l2->next = merge_two(l2->next, l1);
    		return l2;
    	}
    }

    ListNode* merge(vector<ListNode*>& lists, int begin, int end) {
        //condition to end
        if(begin == end) {
            return lists[begin];
        }

        int middle = (begin + end)/2;

        ListNode *l1 = merge(lists, begin, middle);
        ListNode *l2 = merge(lists, middle+1, end);

        return merge_two(l1, l2);
    }
};

複雜度分析

表面看來這兩種做法的時間複雜度是一樣的,但我們運行後發現法一解決問題平均時間爲216ms,而法二需要20ms,下面我們分別分析一下這兩種做法的時間複雜度。

  • 法一
    法一的事件複雜度爲O(k2k^2n)
    第一個和第二個需要時間爲O(n+n)
    前面合併的2n個元素的與下一個合併時間爲O(2n+n)

    最後一步的時間爲O((k-1)n+n)

最後所有時間的和爲O(k2k^2n)

  • 法二
    法二的時間複雜度爲O(nklognk)
    根據大師定理
    有T(k,n)=T([k2\frac{k}{2}],n)+T([k2\frac{k}{2}],n)+O(kn)
    和T(1,n)=O(1)
    可以推出T(k,n)=O(klogkn)

更多技術博客https://vilin.club/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章