算法學習筆記:13.歸并排序——從原理到實戰,涵蓋 LeetCode 與考研 408 例題

歸并排序是一種基于分治策略的經典排序算法,由約翰?馮?諾依曼在 1945 年提出。它以穩定的 O (nlogn) 時間復雜度和良好的可并行性,在大規模數據排序場景中占據重要地位。與快速排序的 “先分區后排序” 不同,歸并排序采用 “先排序后合并” 的思路,其穩定性(相等元素相對順序不變)使其在數據庫排序等對穩定性有要求的場景中備受青睞。

歸并排序算法核心思路

歸并排序的核心思想是分治(Divide and Conquer),即將一個大問題分解為若干個小問題,分別解決后再將結果合并。具體可分為三個步驟:分解(Divide)、治理(Conquer)、合并(Merge)。

關鍵步驟解析

(1)分解(Divide)

將待排序數組不斷二分,直到每個子數組只包含一個元素(此時子數組天然有序)。例如,對數組[8, 4, 5, 7, 1, 3, 6, 2],分解過程如下:

  • 第一層:[8,4,5,7] 和 [1,3,6,2]
  • 第二層:[8,4]、[5,7] 和 [1,3]、[6,2]
  • 第三層:[8]、[4]、[5]、[7]、[1]、[3]、[6]、[2]
(2)治理(Conquer)

遞歸處理每個子數組,由于當子數組長度為 1 時已天然有序,因此這一步主要是遞歸的終止條件。

(3)合并(Merge)

將兩個有序子數組合并為一個更大的有序數組,這是歸并排序的核心步驟。合并過程需借助一個輔助數組,具體步驟如下:

  1. 初始化兩個指針i和j,分別指向兩個子數組的起始位置。
  1. 初始化輔助數組指針k,指向輔助數組起始位置。
  1. 比較i和j指向的元素,將較小的元素放入輔助數組,并移動對應指針。
  1. 重復步驟 3,直到其中一個子數組遍歷完畢。
  1. 將剩余子數組的元素依次放入輔助數組。
  1. 將輔助數組的元素復制回原數組的對應位置。

歸并排序的整體流程

歸并排序的整體流程是 “分解 - 合并” 的遞歸過程,可用以下流程圖表示:

?歸并排序的 Java 實現(基礎版)

public class MergeSortBasic {// 對外暴露的排序方法public static void sort(int[] arr) {if (arr == null || arr.length <= 1) {return;}int[] temp = new int[arr.length]; // 輔助數組(避免遞歸中重復創建)mergeSort(arr, 0, arr.length - 1, temp);}// 遞歸執行歸并排序private static void mergeSort(int[] arr, int left, int right, int[] temp) {if (left < right) {int mid = left + (right - left) / 2; // 避免溢出,等價于(left + right) / 2mergeSort(arr, left, mid, temp); // 左子數組排序mergeSort(arr, mid + 1, right, temp); // 右子數組排序merge(arr, left, mid, right, temp); // 合并}}// 合并兩個有序子數組private static void merge(int[] arr, int left, int mid, int right, int[] temp) {int i = left; // 左子數組起始索引int j = mid + 1; // 右子數組起始索引int k = left; // 輔助數組起始索引// 比較并合并兩個子數組while (i <= mid && j <= right) {if (arr[i] <= arr[j]) { // 保持穩定性(<= 確保相等元素左子數組在前)temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}// 處理左子數組剩余元素while (i <= mid) {temp[k++] = arr[i++];}// 處理右子數組剩余元素while (j <= right) {temp[k++] = arr[j++];}// 將輔助數組復制回原數組for (k = left; k <= right; k++) {arr[k] = temp[k];}}// 測試public static void main(String[] args) {int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};sort(arr);for (int num : arr) {System.out.print(num + " "); // 輸出:1 2 3 4 5 6 7 8}}}

LeetCode 例題實戰

例題 1:912. 排序數組(中等)

題目描述:給你一個整數數組 nums,請你將該數組升序排列。

示例

輸入:nums = [5,2,3,1]

輸出:[1,2,3,5]

解題思路:直接使用歸并排序對數組進行排序。與快速排序相比,歸并排序的時間復雜度穩定為 O (nlogn),適合處理包含大量重復元素或近乎有序的數組。

Java 代碼實現


class Solution {public int[] sortArray(int[] nums) {if (nums == null || nums.length <= 1) {return nums;}int[] temp = new int[nums.length];mergeSort(nums, 0, nums.length - 1, temp);return nums;}private void mergeSort(int[] arr, int left, int right, int[] temp) {if (left < right) {int mid = left + (right - left) / 2;mergeSort(arr, left, mid, temp);mergeSort(arr, mid + 1, right, temp);merge(arr, left, mid, right, temp);}}private void merge(int[] arr, int left, int mid, int right, int[] temp) {int i = left;int j = mid + 1;int k = left;while (i <= mid && j <= right) {if (arr[i] <= arr[j]) {temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}while (i <= mid) {temp[k++] = arr[i++];}while (j <= right) {temp[k++] = arr[j++];}for (k = left; k <= right; k++) {arr[k] = temp[k];}}}

復雜度分析

  • 時間復雜度:O (nlogn),無論數組初始狀態如何,分解和合并的總操作次數均為 O (nlogn)。
  • 空間復雜度:O (n),來自輔助數組temp。

例題 2:315. 計算右側小于當前元素的個數(困難)

題目描述:給你一個整數數組 nums ,按要求返回一個新數組 counts 。數組 counts 有該性質:counts[i] 的值是 nums[i] 右側小于 nums[i] 的元素的數量。

示例

輸入:nums = [5,2,6,1]

輸出:[2,1,1,0]

解釋:

5 的右側有 2 個更小的元素 (2 和 1)

2 的右側有 1 個更小的元素 (1)

6 的右側有 1 個更小的元素 (1)

1 的右側沒有更小的元素

解題思路:本題可借助歸并排序的合并過程統計逆序對。在合并兩個有序子數組時,當右子數組元素nums[j]小于左子數組元素nums[i]時,右子數組中j到mid+1的所有元素均小于nums[i],因此counts[i]需加上right - j + 1(剩余元素個數)。為避免原數組索引被排序打亂,需額外記錄元素原始索引。

Java 代碼實現

class Solution {private int[] counts;public List<Integer> countSmaller(int[] nums) {int n = nums.length;counts = new int[n];if (n == 0) {return new ArrayList<>();}// 構建數組,存儲值和原始索引int[][] arr = new int[n][2];for (int i = 0; i < n; i++) {arr[i][0] = nums[i];arr[i][1] = i;}int[][] temp = new int[n][2]; // 輔助數組mergeSort(arr, 0, n - 1, temp);// 轉換為List返回List<Integer> result = new ArrayList<>();for (int count : counts) {result.add(count);}return result;}private void mergeSort(int[][] arr, int left, int right, int[][] temp) {if (left < right) {int mid = left + (right - left) / 2;mergeSort(arr, left, mid, temp);mergeSort(arr, mid + 1, right, temp);merge(arr, left, mid, right, temp);}}private void merge(int[][] arr, int left, int mid, int right, int[][] temp) {int i = left;int j = mid + 1;int k = left;while (i <= mid && j <= right) {if (arr[i][0] <= arr[j][0]) {// 右子數組中j到right的元素均小于arr[i][0]counts[arr[i][1]] += right - j + 1;temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}// 左子數組剩余元素,右側已無元素,無需計數while (i <= mid) {temp[k++] = arr[i++];}// 右子數組剩余元素,左側已無元素,無需計數while (j <= right) {temp[k++] = arr[j++];}// 復制回原數組for (k = left; k <= right; k++) {arr[k] = temp[k];}}}

復雜度分析

  • 時間復雜度:O (nlogn),歸并排序的時間復雜度為 O (nlogn),合并過程中計數操作僅為 O (1)。
  • 空間復雜度:O (n),來自輔助數組和存儲索引的數組。

考研 408 例題解析

例題 1:基本概念與時間復雜度分析(選擇題)

題目:下列關于歸并排序的敘述中,正確的是( )。

A. 歸并排序的時間復雜度為 O (nlogn),空間復雜度為 O (1)

B. 歸并排序是不穩定的排序算法

C. 歸并排序在最壞情況下的時間復雜度優于快速排序

D. 歸并排序適合對鏈表進行排序

答案:C、D

解析

  • A 錯誤:歸并排序空間復雜度為 O (n)(輔助數組),鏈表排序可優化至 O (1),但數組排序仍為 O (n)。
  • B 錯誤:歸并排序是穩定排序(合并時相等元素按原順序放置)。
  • C 正確:歸并排序最壞時間復雜度為 O (nlogn),而快速排序最壞為 O (n2)。
  • D 正確:鏈表歸并排序無需輔助數組,通過指針操作合并,空間復雜度 O (logn)(遞歸棧),效率高。

例題 2:算法設計題(408 高頻考點)

題目:已知單鏈表的頭指針為head,設計一個算法對該鏈表進行歸并排序,要求時間復雜度為 O (nlogn),空間復雜度為 O (logn)。

解題思路:鏈表歸并排序與數組歸并排序思路一致,但需注意:

  1. 分解:通過快慢指針找到鏈表中點,斷開鏈表為左右兩部分。
  1. 合并:通過指針操作合并兩個有序鏈表,無需輔助數組。

Java 代碼實現

class ListNode {int val;ListNode next;ListNode(int x) {val = x;next = null;}}public class MergeSortList {public ListNode sortList(ListNode head) {// 基線條件:空鏈表或單節點鏈表無需排序if (head == null || head.next == null) {return head;}// 找到鏈表中點并斷開ListNode mid = findMid(head);ListNode rightHead = mid.next;mid.next = null; // 斷開// 遞歸排序左右鏈表ListNode left = sortList(head);ListNode right = sortList(rightHead);// 合并有序鏈表return merge(left, right);}// 快慢指針找中點(偶數節點取左中點)private ListNode findMid(ListNode head) {if (head == null) {return null;}ListNode slow = head;ListNode fast = head.next; // 確保偶數時取左中點while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}return slow;}// 合并兩個有序鏈表private ListNode merge(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode curr = dummy;while (l1 != null < / doubaocanvas >
}

希望本文能夠幫助讀者更深入地理解歸并排序,并在實際項目中發揮其優勢。謝謝閱讀!


希望這份博客能夠幫助到你。如果有其他需要修改或添加的地方,請隨時告訴我。

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

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

相關文章

Kotlin基礎學習記錄

變量和函數 變量 // val為常量&#xff0c;一旦賦值就不可變 val a 10 val a: Int 10 a 3 // 報錯// var為變量 var a 10 a 3 var b: Int 20 b 2函數fun add(a: Int, b: Int): Unit {a b // 報錯&#xff0c;參數默認val }fun add(a: Int, b: Int) {var x: Int ax b …

【C#】GraphicsPath的用法

在 C# 中&#xff0c;GraphicsPath 是 GDI 提供的一個非常強大的類&#xff0c;用于創建和操作復雜圖形路徑。它可以用來繪制直線、曲線、多邊形等形狀&#xff0c;并支持判斷點是否在路徑內或路徑的輪廓上。一、基本概念GraphicsPath 類功能&#xff1a;添加各種幾何圖形&…

C語言32個關鍵字

文章目錄數據類型1、數據類型&#xff08;12個&#xff09;控制語句2、控制語句關鍵字&#xff08;12個&#xff09;存儲類型3、存儲類型關鍵字&#xff08;4個&#xff09;其他關鍵字4、其他關鍵字&#xff08;4個&#xff09;?一共32個關鍵字分為 數據類型 1、數據類型&am…

粒子濾波|粒子濾波的相關算法理論介紹

在自動控制、導航、目標跟蹤等眾多領域&#xff0c;系統狀態估計是獲取真實狀態的關鍵環節。由于觀測信號常受噪聲干擾&#xff0c;濾波技術成為提取可靠信息的核心手段。本文將圍繞目標跟蹤技術中的濾波算法理論展開&#xff0c;重點解析粒子濾波框架的原理與應用。一、動態系…

Jenkins+Gitee+Docker容器化部署

寫在前文 本文主要是通過Jenkins的maven項目版本GiteeDocker-maven插件來進行部署的&#xff0c;本文沒有使用dockerfile/docker-compose。 本文默認已經安裝了Docker 1、安裝Jenkins Step1、創建文件夾當作映射jenkins的home文件夾 mkdir /app/jenkins Step2、賦權&#xff…

[Meetily后端框架] 多模型-Pydantic AI 代理-統一抽象 | SQLite管理

第5章&#xff1a;人工智能模型交互&#xff08;Pydantic-AI 代理&#xff09; 歡迎回來&#xff01; 在上一章第四章&#xff1a;文字記錄處理邏輯中&#xff0c;我們學習了TranscriptProcessor如何將冗長的會議記錄分解為稱為"塊"的較小片段&#xff0c;因為人工…

利用DeepSeek實現rust調用duckdb動態鏈接庫的duckdb CLI

提示詞&#xff1a;請用rust調用duckdb-rs實現一個duckdb CLI,支持語法突出顯示和計時&#xff0c;還支持命令行管道輸入輸出 Cargo.toml [package] name "duckdb-cli" version "0.1.0" edition "2024"[dependencies] duckdb "1.3.1&qu…

C++,從匯編角度看《虛擬繼承的邪惡》

刷到一篇文章&#xff1a; 作者&#xff1a; 原文&#xff1a;虛擬繼承的邪惡 討論到這樣的一個程序&#xff0c;最終輸出什么&#xff1f;&#xff1f;&#xff1f; 代碼有簡化命名 using namespace std;class A { public:A(int a 0) : v(a) {};int v; };template <type…

多 Agent 強化學習實踐指南(一):CTDE PPO 在合作捕食者-獵物游戲中的應用詳解

我們來詳細講解如何在合作捕食者-獵物游戲中結合 PPO (Proximal Policy Optimization) 算法。我們將聚焦于 CTDE&#xff08;Centralized Training, Decentralized Execution&#xff0c;集中訓練、分散執行&#xff09; 模式&#xff0c;因為這是處理合作多 Agent 任務的常用且…

Web應用文件上傳安全設計指南

引言 在當今的Web應用中&#xff0c;文件上傳功能已成為基礎且必要的服務能力&#xff0c;但不當的設計可能帶來目錄遍歷、代碼注入、服務端資源耗盡等安全風險。本文從威脅模型、安全設計原則、技術實現三個維度&#xff0c;系統闡述安全文件上傳架構的設計要點。 一、威脅模型…

用 React Three Fiber 實現 3D 城市模型的擴散光圈特效

本文介紹了如何使用 React Three Fiber&#xff08;R3F&#xff09;和 Three.js 實現一個從中心向外擴散的光圈特效&#xff08;DiffuseAperture 組件&#xff09;&#xff0c;并將其集成到城市 3D 模型&#xff08;CityModel 組件&#xff09;中。該特效通過動態調整圓柱幾何體…

【牛客刷題】COUNT數字計數

文章目錄 一、題目介紹二、題解思路三、算法實現四、復雜度分析五 、關鍵步驟解析5.1 數字分解5.2 三種情況處理5.2.1 情況1: d < c u r d < cur d<cur(完整周期)5.2.2 情況2: d = c u r d = cur d=cur(混合周期)5.2.3 情況3: d > c u r d > cur d>cu…

AGV穿梭不“迷路”CCLinkIE轉Modbus TCP的銜接技巧

在AGV控制系統集成中&#xff0c;工程師常面臨一個現實難題&#xff1a;如何讓CCLinkIE總線與Modbus TCP設備實現高效通信&#xff1f;這種跨協議的連接需求&#xff0c;往往需要耗費大量時間調試。本文將通過實際案例解析&#xff0c;為制造行業工程師提供可復用的解決方案。【…

【代碼隨想錄】刷題筆記——哈希表篇

目錄 242. 有效的字母異位詞 349. 兩個數組的交集 202. 快樂數 1. 兩數之和 454. 四數相加 II 383. 贖金信 15. 三數之和 18. 四數之和 242. 有效的字母異位詞 思路 代碼 class Solution {public boolean isAnagram(String s, String t) {if (s.length() ! t.length()…

Python爬蟲實戰:研究messytables庫相關技術

1. 引言 在當今數字化時代,互聯網上存在著大量有價值的數據。然而,這些數據通常以不規則的格式存在,尤其是表格數據,可能包含復雜的表頭、合并單元格、不規則布局等問題。傳統的數據處理工具往往難以應對這些挑戰。 網絡爬蟲技術可以幫助我們從網頁上自動提取數據,而 mes…

Vue3的組件通信方式

通信方式適用層級數據流向復雜度Props/Emits父子組件單向/雙向★☆☆v-model父子組件雙向★☆☆Provide/Inject跨層級組件自上而下★★☆事件總線任意組件任意方向★★★Pinia/Vuex全局狀態任意方向★★☆Refs模板引用父子組件父→子★☆☆作用域插槽父子組件子→父★★☆Web W…

創客匠人:大健康創始人IP如何用“社會責任”構建品牌護城河

一、商業與責任的失衡困局部分大健康IP將利潤置于首位&#xff0c;甚至犧牲用戶利益&#xff0c;導致品牌形象脆弱。某保健品公司因夸大宣傳被曝光后&#xff0c;盡管銷量曾達千萬&#xff0c;卻因缺乏社會認同&#xff0c;一夜之間崩塌&#xff0c;證明沒有社會責任支撐的商業…

AI:機器人未來的形態是什么?

機器人未來的形態將受到技術進步、應用場景需求和社會接受度的綜合影響&#xff0c;以下是對未來機器人形態的預測&#xff0c;涵蓋技術趨勢、設計方向和應用場景&#xff1a; 1. 形態多樣化與通用化 人形機器人&#xff08;Humanoid Robots&#xff09;&#xff1a; 趨勢&…

創建 UIKit 項目教程

一、打開 XCode&#xff0c;選擇 iOS 下的 App&#xff0c;然后點 Next二、Interface 選擇 Storyboard&#xff0c;然后點 Next三、刪掉 Main.storyboard四、刪掉 SceneDelegate.swift五、AppDelegate.swift 只保留第一個函數六、在 AppDelegate.swift 文件里的 application 函…

防爬蟲君子協定 Robots.txt 文件

1.什么是robots.txt ? robots.txt是一個位于網站根目錄的文本文件,用于指導搜索引擎爬蟲如何訪問和抓取網站內容。它遵循特定的語法規則,是網站與爬蟲通信的重要工具。當搜索引擎訪問一個網站時,它首先會檢查該網站的根域下是否有一個叫做robots.txt的純文本文件。Robots.…