深入淺出多路歸并:原理、實現與實戰案例解析

文章目錄

  • 二路歸并
  • 多路歸并
    • 方法一:指針遍歷(多指針比較法)
    • 方法二:小根堆法(最小堆歸并)
  • 實際場景
    • 外部排序
  • 經典題目
    • 丑數Ⅱ
      • 方法一:三指針法
      • 方法二:優先隊列法(K路歸并)
      • 方法三:優先隊列法(BFS)(非多路歸并)
    • 其他題目
  • 總結

歸并,在計算機科學中,一般是以歸并排序出現的,就是將兩個或者多個有序的序列合并成一個序列。

二路歸并

舉個二路歸并的例子:

輸入兩個有序數組:
[1, 4, 7]
[2, 5, 6, 8]歸并后得到:
[1, 2, 4, 5, 6, 7, 8]

二路歸并實現上很簡單,我們很容易就能想到用兩個指針,分別指向兩個數組的起始位置,然后不斷地兩兩比較指針所指地數值,將小的放到新數組中去,最終遍歷完兩個數組就行了。

多路歸并

但是多路歸并涉及到了多個有序的數組,我們要將這多個有序數組合并成一個。多路歸并一共有兩個方法。

方法一:指針遍歷(多指針比較法)

每個數組維護一個指針,指向當前元素。每次在這 k 個數組當前指向的元素中找到最小值,將其加入結果數組,并將該元素所屬數組的指針后移。這個過程重復進行,直到所有數組遍歷完畢。

  • 每次選擇最小值需要遍歷所有指針,時間復雜度為 O(k)
  • 如果總共有 n 個元素需要合并,時間復雜度為 O(n × k)

方法二:小根堆法(最小堆歸并)

我們使用一個最小堆(優先隊列)來優化每次找最小值的操作:

  1. 首先將每個數組的第一個元素(及其數組索引、元素索引)加入小根堆;
  2. 每次彈出堆頂元素(即當前最小值),將其加入結果數組;
  3. 如果該元素所屬數組還有剩余元素,則將下一個元素加入堆;
  4. 重復直到堆為空。

由于堆的大小最多為 k,每次插入和刪除的時間復雜度是 O(log k),總共處理 n 個元素,因此整體時間復雜度為 O(n × log k),明顯優于指針遍歷法。

假設我們有如下三個有序數組:

text復制編輯arr1 = [1, 4, 7]
arr2 = [2, 5, 8]
arr3 = [3, 6, 9]

使用最小堆歸并過程如下:

  • 初始堆:[1(arr1), 2(arr2), 3(arr3)] → 彈出 1,加入結果,arr1 指針后移,入堆 4;
  • 堆變為:[2(arr2), 3(arr3), 4(arr1)] → 彈出 2,加入結果,入堆 5;
  • 堆變為:[3(arr3), 4(arr1), 5(arr2)] → 彈出 3,加入結果,入堆 6;

實際場景

當我們需要對超大文件(如幾個 GB、甚至 TB 的日志、數據庫快照等)進行排序時,它們無法一次性加載進內存,常規排序算法(如快排、歸并排序)在內存中就不適用了。

于是我們采用 外部排序(External Merge Sort)

外部排序

步驟1: 分段排序

1.先將超大文件分成多份可以加載進內存的小塊。

2.將這k個小塊每一塊都讀進內存進行快速排序,保證每一塊都是有序的。

3.將排序后的每塊都重新記錄回磁盤。

步驟2: 多路歸并

通過步驟1我們得到了k組有序的文件,現在是要把它們合并成一個有序文件。

1.k個文件各自都有一個指針指向當前最小的數據。

2.每次都讀取k個指針所指的數據進入內存。

3.比較出最小的寫入最終的輸出文件sorted_output.txt中。

4.將最小的那個文件的指針后移一位。

重復以上步驟,最終就可以得到排序好的最終輸出文件sorted_output.txt。

經典題目

丑數Ⅱ

讓我們通過一個經典的 LeetCode 問題來看多路歸并的實際應用。

問題描述: 丑數是只包含質因數 2、3、5 的正整數。給定整數 n,返回第 n 個丑數。前幾個丑數序列:1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, …

**問題分析:**分析題目我們得知,丑數是由2,3,5中的任意幾個乘起來得到的。每個丑數都可以由更小的丑數乘以2、3或5得到,并且不會遺漏。

這意味著我們可以構造三個"虛擬"的有序序列:

  • 序列1:所有丑數 × 2
  • 序列2:所有丑數 × 3
  • 序列3:所有丑數 × 5

我們的任務就是對這三個序列進行多路歸并!

方法一:三指針法

維護一個數組 ugly[] 存儲前 n 個丑數,并使用三個指針分別追蹤當前乘以 2、3 和 5 所得到的候選值。每次從候選中選出最小值作為下一個丑數,同時相應地移動對應指針,以避免重復并保持有序。該方法總共找n次,每次需要進行k次的比較,所以最終時間復雜度為 O(n*k),但是因為這道題目的序列個數只有3個,所以其實最終的時間復雜度只有O(n)。

public static int nthUglyNumber(int n) {int[] ugly = new int[n];ugly[0]=1;int i2=0,i3=0,i5=0;int next2=1,next3=1,next5=1;for(int i=1;i<n;i++){next2=ugly[i2]*2;next3=ugly[i3]*3;next5=ugly[i5]*5;ugly[i]=Math.min(next2,Math.min(next3,next5));if(ugly[i]==next2){i2++;}if(ugly[i]==next3){i3++;}if(ugly[i]==next5){i5++;}}return ugly[n-1];
}

方法二:優先隊列法(K路歸并)

上面的方法使用三個指針記錄每一組數當前訪問的元素,然后每次需要找最小數的時候我們進行挨個比較找出最小數。這里我們使用優先隊列來替換掉這個挨個比較找出最小值的過程,我們在優先隊列中保存著三組數當前指針所指的位置,每次都出堆,就能很快地找出最小值了。保證這個堆的大小一直都是k(k為數組個數),總共需要找n次,所以最終的時間復雜度是O(nlogk)。

public static int nthUglyNumber(int n) {int[] ugly = new int[n];ugly[0] = 1;int i2 = 0, i3 = 0, i5 = 0;PriorityQueue<Integer> pq = new PriorityQueue<>();pq.add(2);pq.add(3);pq.add(5);Set<Integer> set = new HashSet<>();set.add(2);set.add(3);set.add(5);for (int i = 1; i < n; i++) {int nextUgly = pq.poll();ugly[i] = nextUgly;if (ugly[i] == ugly[i2] * 2) {i2++;if (!set.contains(ugly[i2] * 2)) {pq.add(ugly[i2] * 2);set.add(ugly[i2] * 2);}}if (ugly[i] == ugly[i3] * 3) {i3++;if (!set.contains(ugly[i3] * 3)) {pq.add(ugly[i3] * 3);set.add(ugly[i3] * 3);}}if (ugly[i] == ugly[i5] * 5) {i5++;if (!set.contains(ugly[i5] * 5)) {pq.add(ugly[i5] * 5);set.add(ugly[i5] * 5);}}}return ugly[n - 1];}

方法三:優先隊列法(BFS)(非多路歸并)

還有一種使用優先隊列的方法來解決這道題目,一開始其實我是用這個方法解決的這道題目,我以為這是多路歸并,但是怎么想怎么不對,現在我認為這是一種廣度優先遍歷的思想。

我設置一個優先隊列,我們從 1 開始,把它看作丑數生成樹的“根節點”。接著每次取出當前最小的一個丑數 ugly,再擴展它的三個“鄰居”:ugly*2, ugly*3, ugly*5,把還沒見過的加入堆中,保證后續訪問。如此往復。直到循環了n次,找到了第n小的丑數。

圖中的 BFS這段丑數代碼中的等價含義
從起點開始遍歷鄰居1 開始擴展出 1*2, 1*3, 1*5
使用隊列維護訪問順序使用 最小堆 PriorityQueue 保證順序
訪問節點后標記為已訪問使用 Set<Long> seen 做去重
一層一層按順序擴展丑數是從小到大、層層構造的
不重復訪問同一節點seen 避免重復乘積
public static int nthUglyNumber(int n) {int ulgy=1;PriorityQueue<Long> pq = new PriorityQueue<>();Set<Long> seen = new HashSet<>();pq.add(1L);seen.add(1L);for(int i=1;i<n;i++){long nextUlgy = pq.poll();if(seen.add(nextUgly*2)){pq.add(nextUgly*2);}if(seen.add(nextUgly*3)){pq.add(nextUgly*3);}if(seen.add(nextUgly*5)){pq.add(nextUgly*5);}}return pq.poll().intValue();
}

其他題目

373. 查找和最小的 K 對數字

313. 超級丑數

632. 最小區間

719. 找出第 K 小的數對距離

786. 第 K 個最小的質數分數

1439. 有序矩陣中的第 k 個最小數組和

1508. 子數組和排序后的區間和

總結

總結來說,多路歸并是一種非常實用的思想,不僅能在傳統排序問題中提升效率,更在很多“按順序生成”類的問題中大放異彩。特別是在內存受限、數據量龐大的現實場景中,它往往是不可替代的選擇。掌握它,也就掌握了不少高頻題的核心解法。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/84460.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/84460.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/84460.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Koji構建系統宏定義注入與Tag體系解析

在Red Hat生態的持續集成鏈條中&#xff0c;Koji作為核心構建系統&#xff0c;其靈活的宏定義機制與精密的Tag體系是保障軟件包高效流轉的關鍵。本文將系統闡述在既有構建目標中注入宏定義的技術路徑&#xff0c;并深度解析Koji中Target與Tag的概念架構及其版本演進差異。 一、…

【Kubernetes】架構與原理:核心概念、組件協同及容器化部署解析

文章目錄 一、前言二、為什么需要 Kubernetes1. 傳統部署方法2. 虛擬化部署3. 容器化部署Ⅰ. 基本概念Ⅱ. 容器編排的必要性Ⅲ. 容器化部署的優勢4. k8s 的歷史與發展三、Kubernetes 基本概念1. k8s 核心架構解析Ⅰ. 控制平面與工作節點Ⅱ. 各組件協同工作原理2. k8s 核心概念Ⅰ…

Pip Manager本地Python包管理器

在Python開發領域&#xff0c;包管理是每個開發者日常工作中不可或缺的一部分。雖然命令行工具pip功能強大&#xff0c;但對于初學者和非技術背景的用戶來說&#xff0c;命令行界面往往顯得不夠友好。如果使用PyCharm&#xff0c;則可以非常簡單的管理安裝的Python包&#xff1…

vscode界面設置透明度--插件Glasslt-VSC

【快捷鍵:透明度提高(CtrAlt Z)&#xff0c;透明度降低(CtrAlt C)】

OPENCV形態學基礎之一膨脹

一.膨脹的原理 數學表達式&#xff1a;dst(x,y) dilate(src(x,y)) max(x,y)src(xx,yy) 膨脹是圖像形態學的基本功能之一&#xff0c;膨脹顧名思義就是求圖像的局部最大值操作&#xff0c;它的數學表達式是dst(x,y) dilate(src(x,y)) max(x,y)src(xx,yy)。 從數學的角度來看…

徹底禁用Windows Defender通知和圖標

方法 一&#xff1a;通過注冊表強制隱藏 Defender 圖標&#xff08;永久生效&#xff09;?? &#xff08;適用于徹底隱藏圖標&#xff0c;但需謹慎操作&#xff09; ??打開注冊表編輯器?? 按 Win R&#xff0c;輸入 regedit 回車。 ??導航到 Defender 相關注冊表項?…

Kafka 2.7.0 單節點安裝與啟動教程(適配 JDK 1.8)

1. 下載與解壓 官方下載 Kafka 2.7.0 https://archive.apache.org/dist/kafka/2.7.0/kafka_2.13-2.7.0.tgz 上傳到虛擬機&#xff08;如 /home/wang/soft/kafka&#xff09;解壓&#xff1a; tar -zxvf kafka_2.13-2.7.0.tgz 2. 配置環境變量&#xff08;可選&#xff0c;便…

23、Python字符串核心機制解析:駐留原理、對象比較與成員檢測實戰

適合人群&#xff1a;零基礎自學者 | 編程小白快速入門 閱讀時長&#xff1a;約5分鐘 文章目錄 一、問題&#xff1a;Python的字符串駐留機制&#xff1f;1、例子1&#xff1a;字符串駐留現象2、答案&#xff1a;&#xff08;1&#xff09;字符串駐留 二、問題&#xff1a;Pyth…

pikachu靶場通關筆記22-2 SQL注入05-2-update注入(報錯法)

目錄 一、SQL注入 二、update注入 三、報錯型注入 四、源碼分析 1、代碼審計 2、滲透思路 五、滲透實戰 1、滲透準備 2、獲取數據庫名database 3、獲取表名table 4、獲取列名column 5、獲取字段 本系列為通過《pikachu靶場通關筆記》的SQL注入關卡(共10關&#xff…

【prometheus+Grafana篇】基于Prometheus+Grafana實現Redis數據庫的監控與可視化

&#x1f4ab;《博主主頁》&#xff1a; &#x1f50e; CSDN主頁 &#x1f50e; IF Club社區主頁 &#x1f525;《擅長領域》&#xff1a;擅長阿里云AnalyticDB for MySQL(分布式數據倉庫)、Oracle、MySQL、Linux、prometheus監控&#xff1b;并對SQLserver、NoSQL(MongoDB)有了…

R語言速釋制劑QBD解決方案之四

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》速釋制劑混合和潤滑工藝研究的R語言解決方案。 原料粒徑分布與混合次數對混合均一性的影響 由于acetriptan 的溶解度低&#xff0c;acetriptan 需要粉碎以提高生物利用度。粉碎后的原料…

用python玩轉大語言模型——從 RNN 到文本生成大語言模型的奇幻之旅

用python玩轉大語言模型——從 RNN 到文本生成大語言模型的奇幻之旅 第一部分:RNN原理及其結構(魔法師的記憶水晶球) 1.1 經典RNN結構(時光旅行者的備忘錄) 核心概念 時間循環:RNN通過隱藏狀態h在時間步之間傳遞信息,形成閉環結構參數共享:每個時間步使用相同的權重…

數據結構(9)排序

一、常見排序算法 排序在生活中無處不在&#xff0c;上學這么多年班級排名啥的總有吧&#xff0c;不可能一次都沒見過&#xff1b;打游戲有的排行榜不也是有排序的思想在里面&#xff0c;排序倒不是什么特殊的數據結構&#xff0c;但是是非常重要的算法思想&#xff0c;所以在初…

量子計算導論課程設計 之 PennyLane環境搭建

文章目錄 具體配置conda 虛擬環境配置Pennylane 正所謂&#xff0c;磨刀不誤砍柴工&#xff0c;想要進行量子計算導論的課程設計&#xff0c;首先就是搭建好平臺&#xff0c;推薦大家就是本地搭建&#xff0c;那么下面有三種選擇 QiskitTensorFlow QuantumPennylane 具體配置…

nginx ./nginx -s reload 不生效

問題 nginx ./nginx -s reload 不生效 解決 不是改opt/nginx下的配置文件是改/usr/local/nginx下的配置文件改之前做好備份

建造者模式深度解析與實戰應用

作者簡介 我是摘星&#xff0c;一名全棧開發者&#xff0c;專注 Java后端開發、AI工程化 與 云計算架構 領域&#xff0c;擅長Python技術棧。熱衷于探索前沿技術&#xff0c;包括大模型應用、云原生解決方案及自動化工具開發。日常深耕技術實踐&#xff0c;樂于分享實戰經驗與…

VScode - 我的常用插件01 - 主題插件Noctis

導言 Noctis 是一款為 Visual Studio Code 提供的主題插件&#xff0c;主打高對比度、護眼、美觀。它有多種配色風格&#xff0c;適合不同的開發者審美和工作場景。 一、安裝Noctis 二、設置顏色主題 三、測試主題 如上所示&#xff0c;有11種主題背景可以選擇。這里&#xff…

【IQA技術專題】圖像質量評價IQA技術和應用綜述(萬字長文!!)

專題介紹 圖像質量評價&#xff08;Image Quality Assessment, IQA&#xff09;是圖像處理、計算機視覺和多媒體通信等領域的關鍵技術之一。IQA不僅被用于學術研究&#xff0c;更在影像相關行業內實現了完整的商業化應用&#xff0c;涉及影視、智能手機、專業相機、安防監控、…

突然虛擬機磁盤只剩下幾十K

第一步&#xff1a;查找哪些文件大于 100M find / -size 100M 第二步&#xff1a;刪除掉無用的 log 發現&#xff0c;磁盤剩余空間并沒有變大 假如一個文件正在被使用&#xff0c;你刪除之后也是不會釋放存儲空間的。需要關閉相應的服務才能釋放。

黑馬教程強化day2-1

目錄 一、Set集合1.Set集合特點2.Set集合分類3.hashSet底層原理&#xff1a;(基于哈希表存儲數據的&#xff09;代碼演示 5.hashSet集合元素的去重操作&#xff08;有些情況搞不動&#xff09;代碼演示 6.LinkedHashSet的底層原理&#xff08;不常用&#xff0c;所以沒有代碼演…