演算法基礎~連結串列~排序連結串列的合併(k條)
1,題意:已知k個已排序連結串列頭結點指標,將這k個連結串列合併,合併後仍然為有序的,返回合併後的頭結點。
2,方法之間時間複雜度的比較:
方法1(藉助工具vector封裝好的sort方法):將k * n個結點放到vector,則原 vector的排序時間複雜度是 O(nlogn);
有k*n個結點的排序,時間複雜度是 O(knlog(kn));
方法2(分制後相連法),分制:這裡我們要想到高中的DNA複製~一個DNA複製生成K個DNA的過程。
【1----複製----》k個數】,複製了n次,結果有k個數,則2n = k。
這裡我們反過來,想到是在融合,則【k個數----複製----》1】,這個過程需要融合的次數,跟當初 複製次數一樣,由2n = k,解得,n=logk。
●具體過程分析:
第一次,連結串列兩兩之間合併,進行 合併次數為 k/2,每次處理結點數字為2n個;
第二次,連結串列兩兩之間合併,進行 合併次數為 k/4,每次處理結點數字為4n個;
。。。
由上面推導的融合過程,知道最後一次,即 第 logk 次,連結串列兩兩之間進行 合併,進行合併次數為
k/2 logk,每次處理結點數字為2 logk n個;
時間複雜度:2n * k/2 + 4n * k/4 + 8n * k/8 + … + 2 logk n * k/2 logk = nk + nk + nk +… +nk = O(nklogk);
所以方法2,更優;
3,從方法2的分析過程,我們深深的感受到一種:把規模大的問題變成規模較小的;規模較小的問題又變成規模更小的問題,
小到一定程度可以直接得出它的解,從而得到問題的解。~沒錯,是遞迴的味道!
4,直接上程式碼,分析如上【程式碼中的mergeTwoLists(連結串列1頭指標,連結串列2頭指標)參考我們上一篇文章:
《演算法基礎~連結串列~排序連結串列的合併(2條)》~ https://www.cnblogs.com/shan333/p/15041561.html】:
public class Solution { public: ListNode* mergeKLists(std::vector<ListNode*>& lists){ if(lists.size() == 0){ return NULL; } if(lists.size() == 1){ return lists[0]; } if(lists.size() == 2){ return mergeTwoLists(lists[0],lists[1]); } int mid = lists.size() / 2; //拆分成兩個子lists std::vector<ListNode*> sub1_lists; std::vector<ListNode*> sub2_lists; for(int i = 0; i < mid; i++){ sub1_lists.push_back(lists[i]); } for(int i = mid; i < lists.size(); i++){ sub2_lists.push_back(lists[i]); } //遞迴,不斷的兩兩連結串列進行融合 ListNode *l1 = mergeKList(sub1_lists); ListNode *l2 = mergeKList(sub2_lists); return mergeTwoLists(l1, l2); } }
5,遞迴思想的使用:
遞迴演算法的思想是:把規模大的問題變成規模較小的;規模較小的問題又變成規模更小的問題,小到一定程度可以直接得出它的解,從而得到問題的解。
解決問題時,把一個問題轉化為一個新的問題,而這個新的問題的解決方法仍與原問題的解法相同,
只是所處理的物件有所不同,這些被處理的物件之間是有規律的遞增或遞減;
參考文章:《什麼情況下用遞迴?~https://blog.csdn.net/ggxxkkll/article/details/7524056》