python 內置函數 sort() 復雜度分析筆記

??在做 280. 擺動排序 時,有一版 python 題解,里面直接用了sort() ,又用了一個簡單的 for 循環,但整體時間復雜度為 O(n?log(n)) ,那么問題就出自這個 sort() ,所以在這分析一下 sort() 的復雜度。

Python 的 list.sort() 用的是 TimSort 算法,而 TimSort 是一個穩定的混合排序算法,結合了歸并排序和插入排序的優點。

那么在最壞情況下, sort() 函數的時間復雜度為什么是 O(n?log(n)) 呢?

前面提到 sort() 用的就是 TimSort 算法,而 TimSort 本質上是一種歸并排序。最壞的情況下,數據完全無序,此時 TimSort 會退化為標準的歸并排序。而歸并排序的時間復雜度是 O(n?log(n))。

先大概總結下歸并排序的算法復雜度由來:

  • 分解:將長為 n 的列表遞歸地分成兩半,這會形成一個深度為 log(n) 的遞歸樹。 → 把規模減半,產生 log n 層遞歸。
  • 合并:每層遞歸中,均需將兩個已排序的子列表合并成一個,這個過程需要遍歷所有元素,因此每層的時間復雜度是 O(n)。 → 每層都要做一次線性合并,每層代價 O(n)。

兩者相乘,得到總的時間復雜度為 O(n?log(n))。

來個例子:
數組 A = [38, 27, 43, 3, 9, 82, 10]1. 分解遞歸地把數組對半劈開,直到只剩單個元素(天然有序):[38 27 43 3 | 9 82 10][38 27 | 43 3] ......[38] [27] [43] [3] [9] [82] [10]2. 合并自底向上把“已排好序的兩段”歸并成更長的一段。合并 [38] 與 [27] → [27 38]合并 [43] 與 [3]  → [3 43]合并 [27 38] 與 [3 43] → [3 27 38 43]右側同理 → [9 10 82]最后把 [3 27 38 43] 與 [9 10 82] 合并 → [3 9 10 27 38 43 82]

現在仔細分析一下,歸并排序的核心思想就是 —— 先把左半邊排好,再把右半邊排好,最后把兩個已排好的部分合并。代碼如下:

// JAVA
public class MergeSort{public static void sort(int[] array) {if(array == null || array.length == 0){return;}mergerSort(array, 0, array.length - 1);}// 遞歸private static void mergeSort(int[] array, int left, int right) {if(left < right){int mid = left + (left + right) / 2;// 遞歸排序左半部分mergeSort(array, left, mid);// 遞歸排序右半部分mergeSort(array, mid + 1, right);// 合并 2 個已排序的部分(歸并)merge(array, left, mid, right);}}// 合并 2 個已排序的部分(將前后兩個相鄰有序表歸并成一個有序表)private static void merge(int[] array, int left, int mid, int right){// 創建臨時數組存儲合并后的結果,所以空間復雜度為 O(n)// 若用 C,可 malloc 申請,注意格式 ElemType *B = (ElemType *)malloc(n*sizeof(ElemType));int[] temp = new int[right - left +1];int i = left; // 左半部分的起始索引int j = mid + 1; // 右半部分的起始索引int k = 0; // 臨時數組的當前索引// 將 2 個部分的元素按順序復制到臨時數組while (i <= mid && j <= right){if (array[i] <= array[j])  temp[k++] = array[i++];  // 可以看出這是一個穩定的排序算法elsetemp[k++] = array[j++];}// 若有剩余部分,都復制到臨時數組while(i <= mid) temp[k++] = array[i++]; // 第1個表未檢測完while(j <= right) temp[k++] = array[j++]; // 第2個表未檢測完// 將排序后的元素從臨時數組復制回原數組中for (k = 0; k < temp.length; k++) {array[left + k] = temp[k];}}// 測試歸并排序函數public static void main(String[] args) {int[] array = {38, 27, 43, 3, 9, 82, 10};sort(array); // 調用歸并排序函數for (int value : array){System.out.print(value + " ");}}
}
# python
def merge_sort(arr):
"""
歸并排序函數
arr: 待排序列表
"""if len(arr) > 1:mid = len(arr) // 2  # 除后向下取整,找到中間索引L = arr[:mid]  # 左半部分[0, mid) 不含 midR = arr[mid:]  # 右半部分[mid, len(arr)-1]含 mid	# 遞歸排序merge_sort(L)merge_sort(R)merge(arr, L, R)  # 合并兩個有序子列表def merge(arr, L, R):
"""
合并兩個已排序子列表到原數組 arr	
arr: 原始數組(最終有序)
L: 左子列表(已排序)
R: 右子列表(已排序)
"""i = j = k = 0# 逐一比較 left 和 right 中的元素,將較小值放入 arrwhile i < len(L) and j < len(R):if L[i] < R[j]:arr[k] = L[i]i += 1else:arr[k] = R[j]j += 1k += 1# 若有剩余部分,都復制到 arr 中while i < len(L):arr[k] = L[i]i += 1k += 1while j < len(R):arr[k] = R[j]j += 1k += 1if __name__ == "__main__":nums = [38, 27, 43, 3, 9, 82, 10]print("排序前:", nums)merge_sort(nums)print("排序后", nums)
注意:Python 切片 arr[:mid]、arr[mid:] 會創建新的列表對象,而非對原數組做“視圖”或指針操作。merge_sort 的每一次遞歸調用里,都額外復制出兩個子列表:L  = arr[:mid]R = arr[mid:]這些臨時列表占用的空間總和在最底層達到 O(n),再加上遞歸棧的 O(log n),整體空間復雜度就是 O(n)。所以,看起來是原地歸并(對原始 arr 直接操作),但實際上產生了額外的空間占用。

那么,下面我們來研究一下這個遞歸棧。

前面說了,歸并排序的時間復雜度為 O(n log n),且 最壞 / 平均 / 最好 三種情況都是這個量級。

所以若是內存允許(歸并空間O(n),快排空間 O(log n)),只考慮時間復雜度,則與快排(平均 O(n log n),最壞O(n2))相比,首選歸并。

長度為 n 的列表,完成歸并排序所需的基本操作次數,設為 T(n)。

merge 階段把兩個已排好序的子序列合并成一個,需要逐元素掃描一次,時間復雜度為 O(n)。

所以完成歸并,總的基本操作次數為

	T(1) = O(1)T(n) = 2T(n/2) + O(n)   (n >= 2)    2·T(n/2):把 n 個元素分成左右兩半,各遞歸排序一次。

遞歸樹的大致結構如下:

層 0:          n  			 (merge 合并開銷 n)	         1 段
層 1:      n/2   n/2 		 (每段合并開銷 n/2+n/2 = n)    2 段
層 2:   n/4 n/4 n/4 n/4 	 (合并開銷還是 n)				 22 段
...			  ...
層 k: 	每段長度 n/2^k,共 2^k 段,合并總開銷仍是 n   		 2^k 段

樹高 h = log? n ,每層合并開銷都是 n,因此總時間:T(n) = n · log? n = O(n log n)

歸并排序的切分點固定在中間,與輸入數據分布無關;合并階段總是線性掃描。因此,最壞 / 平均 / 最好情況的時間復雜度完全一致,都是 O(n log n)。

比較次數 ≈ n log? n ? n + 1(可忽略低階項)。
額外復制:每次 merge 都線性復制一次,但只影響空間,不影響時間復雜度。

空間上:
每次進 merge_sort(arr),遞歸棧也就是調用棧里主要保存:

  • 局部變量 mid
  • 兩個新列表:left, right(切片生成,長度之和≈n)
  • 返回地址、當前代碼行號等解釋器內部信息

2 個子列表本身是在堆上分配的,但它們的 引用 存在棧里,所以每遞歸 1 次,要額外消耗 O(n) 的堆空間;而棧本身只存引用和少量標量 ,因此單個幀的棧空間占用是常數級。


再看遞歸深度:歸并排序每次把區間折半,因此遞歸深度是 depth_max = ?log? n? + 1

遞歸棧空間總量:

  • 棧空間:只存每層的一個常數大小的幀,因此總量是 O(depth_max) = O(log n)
  • 堆空間:由于每層都復制子列表,且同一時刻,最多僅 1 條從根到葉子的路徑上,存在各層副本,因此最大同時占用的堆空間是 n + n/2 + n/4 + … ≈ 2n = O(n)

這就是 歸并排序 的空間復雜度 O(n) 的來源。

歸并排序在任何情況下,時間上都穩定運行在 O(n log n),無退化風險,這也是它被 Python、Java 等語言內置為穩定排序的重要原因。

  1. python 的內置 sort 函數底層基于 Timsort 算法,而 Timsort 算法本質上是一種歸并排序,所以時間復雜度為 O(n log n)
  • 最壞情況下, Timsort 算法時間復雜度為 O(n log n)。
  • 最好情況下,即全局有序 or 接近有序時,Timsort 算法只線性掃描一次,無需歸并,時間復雜度為 O(n)。
  • 平均情況下,Timsort 算法需要 log n 級別的歸并層數,與經典歸并和快排持平,時間復雜度為 O(n log n)。


    綜上,sort 函數時間復雜度為 O(n log n)。
  1. 首先,歸并排序的空間復雜度為 O(n),這個前面有提到。而 Timsort 最好、平均情況下,空間復雜度是 O(log n),最壞情況下是 O(n)。所以 python 的內置 sort 函數空間復雜度與 Timsort 一樣,因為在算法分析里通常按最壞情況報,所以其空間復雜度嚴格來講是 O(n)。

??在實際使用中,尤其是面試或教科書中,Python 的 list.sort() 被視為原地排序,不額外暴露用戶層級的空間使用,因此常簡化為 O(1)。

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

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

相關文章

【光照】Unity中的[經驗模型]

【從UnityURP開始探索游戲渲染】專欄-直達 圖形學第一定律&#xff1a;“看起來對就對” URP光照模型發展史 ?2018年?&#xff1a;URP首次發布&#xff08;原LWRP&#xff09;&#xff0c;繼承傳統前向渲染的Blinn-Phong簡化版?2019年?&#xff1a;URP 7.x引入Basic Shade…

uniapp小程序使用自定義底部tabbar,并根據用戶類型動態切換tabbar數據

1.注意點 在pages.json中配置tabbar如下字段&#xff1a;custom&#xff1a;true &#xff0c;會自動隱藏原生tabbar&#xff0c;使用自定義的tabbar2.如何自定義呢 可以使用第三方組件庫的tabbar組件&#xff0c;然后二次封裝下內部封裝邏輯&#xff1a; 1.點擊切換邏輯 2.根據…

Redis 哨兵 (基于 Docker)

目錄 1. 基本概念 2. 安裝部署 (基于 Docker) 2.1 使用 docker 獲取 redis 鏡像 2.2 編排 主從節點 2.3 編排 redis-sentinel 節點 3. 重新選舉 4. 選舉原理 5. 總結 1. 基本概念 名詞 邏輯結構物理結構主節點Reids 主服務一個獨立的 redis-server 進程從節點Redis 從…

Python學習-day4

Python 語言的運算符: 算術運算符比較&#xff08;關系&#xff09;運算符賦值運算符邏輯運算符位運算符成員運算符身份運算符運算符優先級 算術運算符 定義變量a 21&#xff0c;變量b 10。運算符描述實例加 - 兩個對象相加a b 輸出結果 31-減 - 得到負數或是一個數減去另一…

Vite 插件 @vitejs/plugin-legacy 深度解析:舊瀏覽器兼容指南

&#x1f4d6; 簡介 vitejs/plugin-legacy 是 Vite 官方提供的兼容性插件&#xff0c;專門用于為現代瀏覽器構建的應用程序提供對舊版瀏覽器的支持。該插件通過自動生成兼容性代碼和 polyfill&#xff0c;確保您的應用能夠在 IE 11 等舊版瀏覽器中正常運行。 核心價值 向后兼…

數據質檢之springboot通過yarn調用spark作業實現數據質量檢測

Spring Boot 應用中通過 YARN 來調用 Spark 作業的來執行數據質檢。這是一個非常經典的數據質量檢測、數據優化的常用架構,將Web服務/業務處理(Spring Boot)與大數據質檢(Spark on YARN)解耦。 核心架構圖 首先,通過一張圖來理解整個流程的架構: 整個流程的核心在于,…

SQL優化_以MySQL為例

MySQL SQL 優化詳細教程與案例 1. 理解SQL執行過程 在優化之前&#xff0c;需要了解MySQL如何處理SQL查詢&#xff1a; 客戶端發送SQL語句到服務器服務器檢查查詢緩存&#xff08;MySQL 8.0已移除查詢緩存&#xff09;解析器解析SQL&#xff0c;生成解析樹預處理器驗證權限和表…

探索數據結構中的 “樹”:揭開層次關系的奧秘

在計算機科學的廣袤森林中&#xff0c;有一種數據結構如同參天大樹般支撐著無數應用的根基 —— 它就是 “樹”&#xff08;Tree&#xff09;。它不僅僅是一個抽象概念&#xff0c;更是我們理解和組織信息、模擬現實世界層級關系的強大工具。1. 什么是 “樹”&#xff1f;從家族…

技術框架之RPC

一、序言&#xff1a;為什么我們需要RPC&#xff1f;在單體應用時代&#xff0c;函數調用是進程內的簡單操作。但隨著業務規模擴大&#xff0c;系統被拆分為多個獨立服務&#xff08;如訂單服務、支付服務&#xff09;&#xff0c;服務間通信成為剛需。早期開發者常使用HTTPJSO…

【光照】Unity中的[光照模型]概念辨析

【從UnityURP開始探索游戲渲染】專欄-直達 基礎光照模型? ?標準光照模型&#xff08;Standard Lighting Model&#xff09;? ?定義?&#xff1a;傳統光照計算的框架&#xff0c;通常包含漫反射、鏡面反射和環境光三部分。?特點?&#xff1a;非物理經驗模型&#xff0c…

MCU上跑AI—實時目標檢測算法探索

MCU上跑實時目標檢測算法 前幾年一直忙著別的事情沒有在技術分享上下功夫, 這段時間穩定下來就想和幾個志同道合的朋友做點有意義的事情, 于是乎就使用MCU做了個與AI有識別相關的 “小玩意兒”. 本人負責嵌入式端相關的編碼, AI相關的工作由好友 AgeWang 負責. 這兒把一些成果給…

SpringBoot 整合 RabbitMQ 的完美實踐

引言: 本文總字數:約 9200 字 預計閱讀時間:38 分鐘 為什么 RabbitMQ 是消息中間件的優選? 在分布式系統架構中,消息中間件扮演著 "交通樞紐" 的角色,負責協調各個服務之間的通信。目前主流的消息中間件有 RabbitMQ、Kafka 和 RocketMQ,它們各具特色: Kafka…

nestjs 發起請求 axios

1、下載npm i --save nestjs/axios axios2、全局配置import { HttpModule } from nestjs/axios;Global() Module({imports: [HttpModule.registerAsync({inject: [ConfigService],useFactory: async (configService: ConfigService) > {return {timeout: configService.get(…

將 Logits 得分轉換為概率,如何計算

場景&#xff1a;動物識別&#xff0c;輸入一張28*28的圖像&#xff0c;模型輸出屬于 貓、狗、鳥 哪個類型。需求&#xff1a;假設模型 ??Logits&#xff08;模型在每個類別的置信度得分&#xff09; 輸出為??&#xff1a;[貓: 3.2, 狗: 1.5, 鳥: -0.8]。計算 ??Softmax …

【Qt】bug排查筆記——QMetaObject::invokeMethod: No such method

問題如題目所示&#xff1a;QMetaObject::invokeMethod: No such method xxxx&#xff0c;在網上好一頓查&#xff0c;又將查到的資料喂給了 Ai&#xff0c;才最終將問題解決&#xff0c;特此記錄下。 一、問題背景 在做公司項目時&#xff0c;使用了插件的方式開發。主程序加載…

Spring Boot手寫10萬敏感詞檢查程序

使用Spring Boot手寫10萬敏感詞檢查程序 本文將介紹如何使用Spring Boot構建一個高效的敏感詞檢查系統,能夠處理多達10萬個敏感詞的檢測需求。我們將使用DFA(Deterministic Finite Automaton)算法來實現高效匹配,并提供RESTful API接口。 實現步驟 1. 創建Spring Boot項…

零構建的快感!dagger.js 與 React Hooks 實現對比,誰更優雅?

“Add Tags” 技術方案并行對比&#xff1a;React Hooks vs dagger.js&#xff08;含核心 JS 代碼&#xff09; 源碼&#xff1a; React Hooks&#xff1a;https://codepen.io/prvnbist/pen/jJzROe?editors1010dagger.js&#xff1a;https://codepen.io/dagger8224/pen/ZErjzw…

矩池云中LLaMA- Factory多機多卡訓練

LLaMA Factory 是一款開源低代碼大模型微調框架&#xff0c;集成了業界最廣泛使用的微調技術&#xff0c;支持通過 Web UI 界面零代碼微調大模型&#xff0c;目前已經成為開源社區內最受歡迎的微調框架之一。但是在矩池云上如何使用LLaMA-Factory多機多卡訓練模型呢&#xff1f…

Nginx的反向代理與正向代理及其location的配置說明

一、Nginx中location匹配優先級Nginx中location匹配優先級location支持各種匹配規則&#xff0c;在多個匹配規則下&#xff0c;Nginx對location的處理是有優先級的&#xff0c;優先級高的規則會優先進行處理&#xff1b;而優先級低的規則可能會最后處理或者不進行處理。注意&am…

神經網絡正則化三重奏:Weight Decay, Dropout, 和LayerNorm

正則化是機器學習中防止模型過擬合、提升泛化能力的核心技術。Weight Decay、Dropout和LayerNorm是三種最常用的方法&#xff0c;但它們的工作原理和首要目標截然不同。下面的流程圖揭示了它們的核心區別與聯系&#xff1a; #mermaid-svg-vymek6mFvvfxcWiM {font-family:"…