數據結構(C語言篇):(十三)堆的應用

目錄

?

前言

一、堆排序

1.1? 版本一:基于已有數組建堆、取棧頂元素完成排序

1.1.1? 實現邏輯

1.1.2? 底層原理

1.1.3? 應用示例

1.1.4? 執行流程

1.2? 版本二:原地排序 —— 標準堆排序

1.2.1? 實現邏輯

1.2.2? 底層原理

1.2.3? 時間復雜度計算

二、TOP-K問題

2.1? 實現邏輯

2.2? 底層原理

2.2.1? 為何使用小堆而非大堆?

2.2.2? 時間復雜度優化

2.2.3? 空間復雜度優勢

2.3? 應用示例

2.4? 執行流程


?

前言

????????堆作為一種高效且靈活的樹形結構,在算法設計與優化中扮演著重要角色。其獨特的性質——完全二叉樹形態與父子節點間的有序性,使得堆能夠以對數級時間復雜度動態維護極值,從而為兩類經典問題提供優雅解決方案:堆排序利用大頂堆或小頂堆實現無需遞歸的原地排序,將時間復雜度穩定控制在O(nlogn);而TOP-K問題則通過堆的極值篩選特性,在海量數據場景下以O(nlogk)的代價快速定位關鍵元素。無論是排序場景的性能平衡,還是大數據處理的效率優化,堆結構的應用都展現出算法設計與工程實踐的深度結合。本文將系統解析這兩類問題的技術實現與核心思想。下面就讓我們正式開始吧!


一、堆排序

1.1? 版本一:基于已有數組建堆、取堆頂元素完成排序

1.1.1? 實現邏輯

  1. 創建并初始化堆:定義HP類型的堆變量hp,通過HPInit初始化。
  2. 構建堆:遍歷數組arr,通過HPPush將每個元素插入堆中(插入過程會自動維護堆的性質)。
  3. 提取堆頂元素:循環從堆中獲取堆頂元素(HPTop),依次存入原數組arr,然后通過HPPop刪除堆頂元素。
  4. 銷毀堆:排序完成后調用HPDesTroy釋放堆占用的資源。

? ? ? ? 完整代碼如下所示:

//堆排序----這不是實際的堆排序
void HeapSort01(int* arr,int n)
{HP hp;//-----使用數據結構-堆HPInit(&hp);//調用push將數組中的數據放入到堆中for (int i = 0; i < n; i++){HPPush(&hp, arr[i]);}int i = 0;while (!HPEmpty(&hp)){int top = HPTop(&hp);arr[i++] = top;HPPop(&hp);}HPDesTroy(&hp);
}

1.1.2? 底層原理

? ? ? ? 排序過程的本質是這樣的:

  • 若使用小堆:每次提取的堆頂是當前剩余元素的最小值,依次存入數組會得到升序結果
  • 若使用大堆:每次提取的堆頂是當前剩余元素的最大值,依次存入數組(需從后往前存)會得到升序結果

? ? ? ? 這種堆排序方法的整體時間復雜度為,時間復雜度的計算主要需要考慮下面兩種操作:

  • 構建堆:nHPPush,每次,總復雜度
  • 提取元素:nHPTop)和HPPop),總復雜度

1.1.3? 應用示例

????????假設堆為小堆(AdjustUpAdjustDown均按小堆邏輯實現),則應用示例如下所示:

// 假設已實現HPTop函數(獲取堆頂元素)
HPDataType HPTop(HP* php) {assert(php && !HPEmpty(php));return php->arr[0];
}// 排序示例
int main() {int arr[] = {3, 1, 4, 1, 5, 9, 2, 6};int n = sizeof(arr) / sizeof(arr[0]);HeapSort01(arr, n);// 輸出排序結果(升序)for (int i = 0; i < n; i++) {printf("%d ", arr[i]); // 輸出:1 1 2 3 4 5 6 9}return 0;
}

1.1.4? 執行流程

????????以數組[3, 1, 2]為例,執行流程如下所示:

? ? ? ? (1)初始化堆:HPInit(&hp)創建空堆。

? ? ? ? (2)元素入堆:

  • 插入 3:堆為[3];
  • 插入 1:經AdjustUp調整后堆為[1, 3];
  • 插入 2:經AdjustUp調整后堆為[1, 3, 2](小堆性質:1≤3 且 1≤2)。

? ? ? ? (3)元素出堆存回數組:

  • 第一次:HPTop獲取 1,存入arr[0]HPPop后堆變為[2, 3];
  • 第二次:HPTop獲取 2,存入arr[1]HPPop后堆變為[3];
  • 第三次:HPTop獲取 3,存入arr[2]HPPop后堆為空。

? ? ? ? (4)銷毀堆:HPDesTroy(&hp)釋放資源,排序完成,數組變為[1, 2, 3]

? ? ? ? 為什么說這種堆排序“不是實際的堆排序”呢?


? ? ? ? 這種方法實現的堆排序與標準堆排序的核心區別在于空間效率:

? ? ? ? 標準堆排序是直接在原數組上構建堆(原地建堆)的,通過 "堆頂與末尾元素交換 + 向下調整" 實現排序,不需要額外的堆結構,空間復雜度為

? ? ? ? 而這種堆排序需要額外創建一個堆結構存儲所有元素,空間復雜度為,本質是 "借助堆的特性實現排序",而非嚴格意義上的原地堆排序。

? ? ? ? 除此之外,標準堆排序的建堆過程是通過AdjustDown從后往前調整(時間復雜度為),而該函數通過HPPush建堆(時間復雜度為),建堆效率是比較低的。

1.2? 版本二:原地排序 —— 標準堆排序

? ? ? ? 標準堆排序的核心思路在于:數組建堆,首尾交換,交換后的堆尾數據從堆中刪掉,將堆頂數據向下調整選出次大的數據。

1.2.1? 實現邏輯

? ? ? ? 標準堆排序的核心操作主要分為兩個階段:

(1)建堆階段:

? ? ? ? 首先從最后一個非葉子節點(從索引(n-1-1)/ 2)開始,向前遍歷至根節點;

? ? ? ? 然后對于每個結點執行AdjustDown(向下調整),將無序數組轉換為堆(默認實現為大堆,便于升序排序)。

(2)排序階段:

  1. 定義end指向當前堆的最后一個元素(初始為n-1);
  2. 循環交換堆頂(索引0,當前最大值)與end位置的元素,將最大值 "沉" 到數組末尾;
  3. 對新堆頂(原end位置元素)執行AdjustDown,調整范圍為[0, end)(排除已排好的末尾元素);
  4. 減小end,重復操作直到end0(所有元素排序完成)。

? ? ? ? 完整代碼的實現如下:

//堆排序————使用的是堆結構的思想 n * logn
void HeapSort(int* arr, int n)
{//向下調整算法——建堆nfor (int i = (n - 1 - 1)/2; i >= 0; i--){AdjustDown(arr, i, n);}////向上調整算法建堆n*logn//for (int i = 0; i < n; i++)//{//	AdjustUp(arr, i);//}//n*lognint end = n - 1;while (end > 0){Swap(&arr[0], &arr[end]);AdjustDown(arr, 0, end);//lognend--;}
}

1.2.2? 底層原理

  • 建堆的選擇

? ? ? ? 標準堆排序優先使用AdjustDown從后往前建堆,時間復雜度為

? ? ? ? 上述代碼中的AdjustUp從前往后建堆,時間復雜度為,因此前者更高效;

????????建堆選擇大堆的原因:大堆的堆頂是最大值,每次交換后可將最大值放到數組末尾,直接形成升序。

  • 原地排序的實現

????????無需額外空間,直接在原數組上通過索引操作實現堆結構(利用完全二叉樹的父子節點索引關系);

????end變量的作用是劃分 "未排序的堆區域" 和 "已排序的末尾區域",隨著循環逐漸縮小堆區域。

1.2.3? 時間復雜度計算

????????

? ? ? ? 分析如下:

? ? ? ? 第1層,有個結點,交換到根結點后,需要向下移動0層;

????????第2層,有個結點,交換到根結點后,需要向下移動1層;

????????第3層,有個結點,交換到根結點后,需要向下移動2層;

? ? ? ? ……

????????第h層,有個結點,交換到根結點后,需要向下移動h-1層。

? ? ? ? 由此我們可以發現,堆排序第二個循環中的向下調整與建堆中的向上調整算法的時間復雜度計算是一致的,故在這博主就不再過多贅述了。大家如果感興趣的話可以去看看我上一期的博客。

? ? ? ? 最終可以計算得到堆排序的時間復雜度為
?

二、TOP-K問題

? ? ? ? TOP-K問題就是求數據結合中前K個最大的元素或者最小的元素,一般情況下數據量都是比較大的。

? ? ? ? 比如:專業前10名、世界500強、富豪榜、游戲中前100的活躍玩家等。

? ? ? ? 對于TOP-K問題,我們能夠想到的最簡單直接的方法就是進行排序。但是,如果數據量非常大時,排序的效率就會變得很低(可能出現數據不能一下全部加載到內存中的情況)。因此解決TOP-K問題的最佳方式是使用堆,基本思路如下:

(1)用數據集合中的前K個元素來建堆:

? ? ? ? 如果用前K個最大的元素,則建小堆;如果用前K個最小的元素,則建大堆。

(2)用剩余的N-K個元素依次與堆頂元素來比較,不滿足則替換堆頂元素:

? ? ? ? 將剩余N-K個元素依次與堆頂元素比完之后,堆中剩余的K個元素就是所求的前K個最小或者最大的元素。

? ? ? ? 要解決TOP-K問題,我們需要先利用C語言篇學到的文件操作的相關知識,來造一些數據:

void CreateNDate()
{// 造數據int n = 100000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (int i = 0; i < n; ++i){int x = (rand() + i) % 1000000;fprintf(fin, "%d\n", x);}fclose(fin);
}

? ? ? ? 這樣我們就生成了100000個隨機數,并把它們存儲在了“data.txt”文件中。

? ? ? ? 下面我們就來正式嘗試解決一下TOP-K問題:

2.1? 實現邏輯

(1)獲取用戶輸入K:通過scanf讀取需要查找的前 K 個元素數量。

(2)文件操作初始化:打開數據文件data.txt,處理文件打開失敗的異常。

(3)創建小堆:

  • 動態分配大小為 K 的數組minHeap(用于存儲候選的前 K 個最大元素);
  • 從文件中讀取前 K 個數據存入數組;
  • 通過AdjustDown將數組調整為小堆(堆頂為當前 K 個元素中的最小值)。

(4)篩選剩余數據:

  • 遍歷文件中剩余的所有數據;
  • 若當前數據大于小堆的堆頂(說明該數據比當前候選的最小元素更大,有資格進入前 K),則替換堆頂并通過AdjustDown重新調整為小堆。

(5)輸出結果:循環結束后,小堆中存儲的就是整個文件中最大的前 K 個元素,打印輸出。

(6)資源釋放:關閉文件。

? ? ? ? 完整代碼如下所示:

void TopK()
{int k = 0;printf("請輸入K:");scanf("%d", &k);const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen fail!");exit(1);}//申請空間大小為k的整型數組int* minHeap = (int*)malloc(sizeof(int) * k);if (minHeap == NULL){perror("malloc fail!");exit(2);}//讀取文件中k個數據放到數組中for (int i = 0; i < k; i++){fscanf(fout, "%d", &minHeap[i]);}//數組調整建堆--向下調整建堆//找最大的前K個數,建小堆for (int i = (k-1-1)/2; i >= 0; i--){AdjustDown(minHeap, i, k);}//遍歷剩下的n-k個數,跟堆頂比較,誰大誰入堆int data = 0;while (fscanf(fout,"%d",&data) != EOF){if (data > minHeap[0]){minHeap[0] = data;AdjustDown(minHeap, 0, k);}}//打印堆里的數據for (int i = 0; i < k; i++){printf("%d ", minHeap[i]);}printf("\n");fclose(fout);
}

2.2? 底層原理

2.2.1? 為何使用小堆而非大堆?

? ? ? ? 我們的目標是找 “最大的前 K 個元素”,小堆的堆頂是當前候選集中的最小值。當新元素大于堆頂時,說明它比候選集中最小的元素更大,是有資格替換堆頂進入候選集的。

????????若使用大堆,堆頂是候選集中的最大值,是無法通過簡單比較判斷新元素是否應進入候選集的(新元素可能比堆頂小但比其他元素大)。

2.2.2? 時間復雜度優化

????????在傳統方法中,是對所有數據排序后取前 K 個,時間復雜度為(n 為數據總量)。

? ? ? ? 在堆解法中,建堆時間為,遍歷剩余數據并調整堆的時間為,整體復雜度為。當 n 極大(如百萬 / 億級)且 K 較小時(如 100),性能優勢是十分顯著的。

2.2.3? 空間復雜度優勢

? ? ? ? 堆解法僅需要的空間存儲小堆,適合處理無法一次性加載到內存的海量數據(如大文件)。

2.3? 應用示例

????????假設data.txt中存儲以下數據(共 10 個元素):

5 9 3 12 7 15 2 8 11 6

????????當用戶輸入k=3時,期望輸出最大的 3 個元素:15、12、11。

????????首先讀取前 3 個數據[5, 9, 3],建小堆后為[3, 9, 5](堆頂 3 是當前最小值)

? ? ? ? 接著遍歷剩余數據:

????????????????12 > 3 → 替換堆頂為 12,調整后小堆為[5, 9, 12];

????????????????7 > 5 → 替換堆頂為 7,調整后小堆為[7, 9, 12];

????????????????15 > 7 → 替換堆頂為 15,調整后小堆為[9, 15, 12];

????????????????2 ≤ 9 → 不處理;

????????????????8 ≤ 9 → 不處理;

????????????????11 > 9 → 替換堆頂為 11,調整后小堆為[11, 15, 12];

????????????????6 ≤ 11 → 不處理。

????????最終小堆[11, 15, 12]中存儲的就是最大的 3 個元素(輸出順序可能因堆結構而略有不同)。

2.4? 執行流程

(1)輸入與初始化:

  • 用戶輸入 K 值(如 3);
  • 打開data.txt,分配大小為 K 的數組minHeap。

(2)構建初始小堆:

  • 讀取前 K 個數據到minHeap;
  • 從最后一個非葉子節點(k-1-1)/2開始,通過AdjustDown將數組調整為小堆(確保堆頂是當前最小值)。

(3)篩選剩余數據:

  • 循環讀取文件中剩余的每個數據data
    • data > 堆頂(說明data比當前候選集中最小的元素大):
      • data替換堆頂元素;
      • 調用AdjustDown從堆頂開始調整,維持小堆性質。
    • 否則:跳過該數據。

(4)輸出結果:

????????小堆中保存的 K 個元素即為整個文件中最大的前 K 個,打印輸出。

? ? ? ? 最終我們可以得到TOP-K問題的時間復雜度為:

?

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

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

相關文章

4步OpenCV-----掃秒身份證號

這段代碼用 OpenCV 做了一份“數字模板字典”&#xff0c;然后在銀行卡/身份證照片里自動找到身份證號那一行&#xff0c;把每個數字切出來跟模板比對&#xff0c;最終輸出并高亮顯示出完整的身份證號碼&#xff0c;下面是代碼解釋&#xff1a;模塊 1 工具箱&#xff08;通用函…

馮諾依曼體系:現代計算機的基石與未來展望

馮諾依曼體系&#xff1a;現代計算機的基石與未來展望 引人入勝的開篇 當你用手機刷視頻、用電腦辦公時&#xff0c;是否想過這些設備背后共享的底層邏輯&#xff1f;從指尖輕滑切換APP&#xff0c;到電腦秒開文檔&#xff0c;這種「無縫銜接」的體驗&#xff0c;其實藏著一個改…

前端基礎 —— C / JavaScript基礎語法

以下是對《3.JavaScript(基礎語法).pdf》的內容大綱總結&#xff1a;---&#x1f4d8; 一、JavaScript 簡介 - 定義&#xff1a;腳本語言&#xff0c;最初用于表單驗證&#xff0c;現為通用編程語言。 - 應用&#xff1a;網頁開發、游戲、服務器&#xff08;Node.js&#xff09…

springboot 二手物品交易系統設計與實現

springboot 二手物品交易系統設計與實現 目錄 【SpringBoot二手交易系統全解析】從0到1搭建你的專屬平臺&#xff01; &#x1f50d; 需求確認&#xff1a;溝通對接 &#x1f5e3; &#x1f4ca; 系統功能結構&#xff1a;附思維導圖 ☆開發技術&#xff1a; &#x1f6e…

【Android】可折疊式標題欄

在 Android 應用開發中&#xff0c;精美的用戶界面可以顯著提升應用品質和用戶體驗。Material Design 組件中的 CollapsingToolbarLayout 能夠為應用添加動態、流暢的折疊效果&#xff0c;讓標題欄不再是靜態的元素。本文將深入探討如何使用 CollapsingToolbarLayout 創建令人驚…

Debian13下使用 Vim + Vimspector + ST-LINK v2.1 調試 STM32F103 指南

1. 硬件準備與連接 1.1 所需硬件 STM32F103C8T6 最小系統板ST-LINK v2.1 調試器連接線&#xff08;杜邦線&#xff09; 1.2 硬件連接 ST-LINK v2.1 ? STM32F103C8T6 連接方式&#xff1a;ST-LINK v2.1 引腳STM32F103C8T6 引腳功能說明SWDIOPA13數據線SWCLKPA14時鐘線GNDGND共地…

第21課:成本優化與資源管理

第21課:成本優化與資源管理 課程目標 掌握計算資源優化 學習成本控制策略 了解資源調度算法 實踐實現成本優化系統 課程內容 21.1 成本分析框架 成本分析系統 class CostAnalysisFramework {constructor(config) {this.config

SAP HANA Scale-out 04:CalculationView優化

CV執行過程計算視圖激活時&#xff0c;生成Stored ModelSELECT查詢時&#xff1a;首先將Stored Model實例化為runtime Model 計算引擎執行優化&#xff0c;將runtime Model轉換為Optimized Runtime ModelOptimized Runtime Model通過SQL Optimizer進行優化計算引擎優化特性說明…

鴻蒙審核問題——Scroll中嵌套了List/Grid時滑動問題

文章目錄背景原因解決辦法1、借鑒Flutter中的解決方式&#xff0c;如下圖2、鴻蒙Next中對應的解決方式&#xff0c;如下圖3、官方文檔回訪背景 來源一次審核被拒的情況。也是出于粗心導致的。之前在flutter項目中也是遇到過這種問題的。其實就是滾動視圖內嵌滾動視圖造成的&am…

測試電商購物車功能,設計測試case

在電商場景中&#xff0c;購物車是連接商品瀏覽與下單支付的關鍵環節&#xff0c;需要從功能、性能、兼容性、安全性等多維度進行測試。以下是購物車功能的測試用例設計&#xff1a; 一、功能測試 1. 商品添加到購物車 - 未登錄狀態下&#xff0c;添加商品到購物車&#xff08;…

Linux --- 常見的基本指令

一. 前言本篇博客使用的 Linux 操作系統是 centos &#xff0c;用來學習Linux 的 Linux 系統的內核版本和系統架構信息版本如下所示&#xff1a;上圖的主要結構為&#xff1a;主版本號-次版本號 修正次數&#xff0c;3.10.0 是操作系統的主版本號&#xff1b;當我們在維護一段L…

微信小程序 -開發郵箱注冊驗證功能

一、前端驗證&#xff1a;正則表達式與插件結合正則表達式設計 使用通用郵箱格式校驗正則&#xff0c;并允許中文域名&#xff08;如.中國&#xff09;&#xff1a; const emailReg /^[a-zA-Z0-9._%-][a-zA-Z0-9-](?:\.[a-zA-Z0-9-])*\.[a-zA-Z]{2,}(?:\.[a-zA-Z]{2})?$/i;…

docker 部署 code-server

docker 部署 code-servercode-serverError response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headersdocker 配置正確步驟 阿里云源permission de…

網絡編程專題:從源碼解析網絡編程常用方法(基于6.16.3內核)

前言 本文是因為作者在研究下面這個代碼時發現的問題&#xff1a; int main() {// 1. 創建 IPv4 專用地址結構體 sockaddr_instruct sockaddr_in ipv4_addr;memset(&ipv4_addr, 0, sizeof(ipv4_addr)); // 初始化清零// 2. 填充 IPv4 專屬信息ipv4_addr.sin_family AF_IN…

2025年數字公共治理專業重點學什么內容?(詳細指南)

數字公共治理作為一個新興的跨學科領域&#xff0c;近年來受到越來越多高校和學生的關注。這個專業融合了多個學科的知識體系&#xff0c;旨在培養掌握現代治理理念和技術應用能力的復合型人才。對于在校大學生而言&#xff0c;了解這一專業的學習內容和發展方向&#xff0c;有…

一招解決 win 下 終端打印中文亂碼問題

適合所有終端 cmd powershell git bash&#xff0c; 原理&#xff1a;修改電腦的區域設置&#xff0c;勾選使用 UTF-8 1.電腦搜索 區域&#xff0c; 打開區域設置2. 打開相關設置3. 點擊更改 日期、時間或數字格式4. 選則管理-點擊更改系統區域設置&#xff0c;在彈出框中勾選 …

Elasticsearch面試精講 Day 13:索引生命周期管理ILM

【Elasticsearch面試精講 Day 13】索引生命周期管理ILM 在“Elasticsearch面試精講”系列的第13天&#xff0c;我們將深入探討 索引生命周期管理&#xff08;Index Lifecycle Management, ILM&#xff09; 這一核心運維機制。作為大規模日志、監控和時序數據場景下的必備功能&…

Python快速入門專業版(二十八):函數參數進階:默認參數與可變參數(*args/**kwargs)

目錄引一、默認參數&#xff1a;給函數參數設置“默認值”1. 基本語法與使用示例示例1&#xff1a;帶默認參數的乘法函數2. 默認參數的核心規則&#xff1a;必須放在非默認參數之后示例2&#xff1a;默認參數位置錯誤&#xff08;報錯&#xff09;3. 默認參數的“可變對象陷阱”…

FreeRTOS 知識點

一、配置過程二、基本知識點2.1 搶占優先級和響應優先級在 FreeRTOS 中&#xff0c;任務的調度方式主要有 ??搶占式&#xff08;Preemptive&#xff09;?? 和 ??協作式&#xff08;Cooperative&#xff09;?? 兩種模式&#xff0c;它們的核心區別在于 ??任務如何釋放…

SQL注入漏洞手動測試詳細過程

這是一次詳細的、基于真實手動測試思維的SQL注入漏洞測試過程記錄。我們將以一個假設的Web應用程序為例&#xff0c;進行逐步探測和利用。測試目標假設我們正在測試一個名為 example.com 的電商網站&#xff0c;其有一個查看商品詳情的頁面&#xff0c;URL 為&#xff1a; http…