[數據結構——lesson10.2堆排序以及TopK問題]

目錄

前言

學習目標

堆排序

TopK問題:

解法一:建立N個數的堆

解法二:建立K個數的堆(最優解)

完整代碼

結束語


前言

上節內容我們詳細講解了堆[數據結構——lesson10.堆及堆的調整算法],接下來我們來講解堆的一個經典應用——TopK問題

學習目標

  • 堆排序
  • 掌握堆的應用理解TopK問題

兩種調整算法的復雜度精準剖析🌟🌟🌟

開頭講了兩種堆的調整算法,分別是【向上調整】和【向下調整】,在接口算法實現Push和Pop的時候又用到了它們,以及在建堆這一塊我也對它們分別做了一個分析,所以我們本文的核心就是圍繞這兩個調整算法來的,但是它們兩個到底誰更加優一些呢?
這里就不做過多解釋,直接看圖即可


1、向下調整算法【重點掌握】
?

2、向上調整算法

好,我們來總結一下,【向上調整算法】,它的時間復雜度為O(NlogN);【向下調整算法】,它的時間復雜度為O(N)
很明顯,【向下調整算法】來得更優一些,因為向下調整隨著堆的層數增加結點數也會變多,可是結點越多調整得就越少,因為在一些大型數據處理場合我們會使用向下調整
當然在下面要講的堆排序中我們建堆也是利用的向下調整算法,所以大家重點掌握一個就行
?

堆排序

講了那么久的堆,學習了兩種調整算法以及它們的時間復雜度分析,接下去我們來說說一種基于堆的排序算法——【堆排序】

堆排序即利用堆的思想來進行排序,總共分為兩個步驟:
1. 建堆
  • 升序:建大堆
  • 降序:建小堆
這里我們以升序為例(升序建大堆 or 小堆?)
  • 在上面解說的時候,我建立的默認都是大堆,但是在這里我們要考慮排序問題了,現在面臨的是【升序】,對于升序就是數組前面的元素小,后面的元素大,這個堆也是基于數組建立的,那就是要堆頂小,堆頂大,很明顯就是建【小堆】
  • 一波分析猛如虎🐅,我們通過畫圖來分析是否可以建【小堆】

  • 可以看到,對于建小堆來說,原本的左孩子結點就會變成新的根結點,而右孩子結點就會變成新的左孩子結點,整個堆會亂,而且效率并不是很高,因此我們應該反一下,去建大堆
//建立大根堆(倒數第一個非葉子結點)
for (int i = ((n - 1) - 1) / 2 ; i >= 0; --i)
{Adjust_Down(a, n, i);
}

所以應建大堆

?

堆排序的基本思路是每次將堆頂元素取出放到有序區間。大堆(大頂堆)中每個節點的值都大于或等于其子節點的值,堆頂元素為最大值。升序排序時建大堆,可將堆頂的最大值與無序區間最后一個數交換,使有序區間增加一個最大值。然后對剩余元素重新調整堆結構,重復此過程,就能逐漸將序列變為升序。

?

若升序建小堆,雖然堆頂是最小值,但確定次小值時會很麻煩,因為次小值可能是堆頂的左孩子或右孩子,甚至需要重新建堆,導致排序效率降低。

如何進一步實現排序?
  • 有了一個大堆之后,如何去進一步實現升序呢,這里就要使用到在Pop堆頂數據的思路了,也就是現將堆頂數據與堆底末梢數據做一個交換,然后對這個堆頂數據進行一個向下調整,將大的數往上調。具體過程如下
2. 利用堆刪除思想來進行排序
建堆和堆刪除中都用到了向下調整,因此掌握了向下調整,就可以完成堆排序。

  • 對照代碼,好好分析一下堆排的全過程吧
/*堆排序*/
void HeapSort(int* a, int n)
{//建立大根堆(倒數第一個非葉子結點)for (int i = ((n - 1) - 1) / 2 ; i >= 0; --i){Adjust_Down(a, n, i);}int end = n - 1;while (end > 0){swap(&a[0], &a[end]);		//首先交換堆頂結點和堆底末梢結點Adjust_Down(a, end, 0);		//一一向前調整end--;}
}
  • 看一下時間復雜度,建堆這一塊是O(N),調整這一塊的話就是每次夠把當前堆中最的數放到堆底來,然后每一個次大的數都需要向下調整O(log2N),數組中有N個數需要調整做排序,因而就是O(Nlog2N)
  • 當然你可以這么去看:第一次放最大的數,第二次是次大的數,這其實和我們上面講過的向上調整差不多了,【結點越少,調整越少;結點越多,調整越多】,因此它也可以使用之前我們分析過的使用的【錯位相減法】去進行求解,算出來也是一個O(Nlog2N)
  • 最后將兩段代碼整合一下,就是O(N + Nlog2N),取影響結果大的那一個就是O(Nlog2N),這也就是堆排序最終的時間復雜度


TopK問題:

Top-K 問題是一類常見的算法和數據處理問題,指從包含 N 個元素的大量數據集合中找到前 K 個最大或最小的元素,通常 N 遠大于 K。

Top-k問題在生活中是非常的常見,比如游戲中某個大區某個英雄熟練度最高的前10個玩家的排名,我們就要根據每個玩家對該英雄的熟練度進行排序,可能有200萬個玩家,但我只想選出前10個,要對所有人去排個序嗎?顯然沒這個必要。

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

  • 問題特點:數據量 N 極大時,若直接對所有數據排序(如快排,時間復雜度為 O (n log n)),不僅耗時久,還可能需將所有數據加載到內存,空間成本極高。而 K 通常很小,只需關注 “最大或最小的前 K 個”,無需對所有數據排序,因此需要更高效的算法來解決。

解法一:建立N個數的堆

建一個 N 個數的堆(C++中可用優先級隊列priority_queue),不斷的選數,選出前 k 個。

時間復雜度:建N個數的堆為O(N),獲取堆頂元素 (也即是最值) 并刪除掉堆頂元素為O(log2N),上述操作重復 k 次,所以時間復雜度為O(N+k*log2N)

【思考】

但是這樣也會存在上述所講的可能需將所有數據加載到內存,空間成本極高的問題,能否再優化一下呢?


解法二:建立K個數的堆(最優解)

解決思路堆排序

  • 若要找前 K 個最大的元素,則建立小頂堆
  • 若要找前 K 個最小的元素,則建立大頂堆
  • 首先用數據集合中前 K 個元素來建堆,然后將剩余的 N-K 個元素依次與堆頂元素比較;
  • 大于(針對小頂堆)小于(針對大頂堆)堆頂元素,則替換堆頂元素并重新調整堆;
  • 遍歷完剩余元素后,堆中的 K 個元素就是所求的前 K 個最大或最小的元素。

時間復雜度:

? 建 k 個元素的堆為O(K);
? 遍歷剩余的 N-K 個元素的時間代價為O(N-K),假設運氣很差,每次遍歷都入堆調整;
? 入堆調整:刪除堆頂元素和插入元素都為O(log2K);
? 所以時間復雜度為O(k + (N-K)log2K)。當 N 遠大于 K 時,為O(N*log2K),這種解法更優。

?

假如要找出最大的前 10 個數:

? 建立 10 個元素的小堆,數據集合中前 10 個元素依次放入小堆,此時的堆頂元素是堆中最小的元素,也是堆里面第 10 個最小的元素,
? ?然后把數據集合中剩下的元素與堆頂比較,若大于堆頂則去掉堆頂,再將其插入,
? ?這樣一來,堆里面存放的就是數據集合中的前 10 個最大元素,
此時小堆的堆頂元素也就是堆中的第 10 個最大的元素

?

思考:為什么找出最大的前10個數,不能建大堆呢?

  • 找出最大的前 10 個數不能建大堆,原因在于大堆的特性會導致只能找到最大的數,而無法找到其余較大的數。
  • 大堆的性質是堆頂元素為堆中最大的元素。當使用 10 個元素建大堆時,堆頂就是這 10 個元素中最大的,若數據集合中還有其他更大的數,由于它們都小于當前堆頂元素,根據大堆的插入規則,這些數無法進入堆中。所以最終只能得到最大的那個數,無法找出前 10 個最大的數。
  • 相反,若建立小堆,堆頂是堆中最小的元素,當有比堆頂大的元素出現時,就可以替換堆頂元素,并通過調整堆結構使小堆性質得以維持,這樣就能保證較大的數逐漸進入堆中,最終堆中的 10 個元素就是數據集合中前 10 個最大的數。

完整代碼

以從1w個數里找出最大的前10個數為例:

#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<stdio.h>
#include<time.h>// 大堆調整
void max_heapify(int* arr, int i, int size)
{int largest = i;int left = 2 * i + 1;int right = 2 * i + 2;if (left < size && arr[left] > arr[largest])largest = left;if (right < size && arr[right] > arr[largest])largest = right;if (largest != i){int temp = arr[i];arr[i] = arr[largest];arr[largest] = temp;max_heapify(arr, largest, size);}
}// 構建大根堆
void build_max_heap(int* arr, int size)
{for (int i = size / 2 - 1; i >= 0; i--)max_heapify(arr, i, size);
}// 堆排序
void heap_sort(int* arr, int size)
{build_max_heap(arr, size);for (int i = size - 1; i > 0; i--) {int temp = arr[0];arr[0] = arr[i];arr[i] = temp;max_heapify(arr, 0, i);}
}// 獲取前k個最小元素
void get_topk_smallest(int* arr, int n, int k, int* result)
{if (k > n) k = n;int* heap = (int*)malloc(k * sizeof(int));if (heap == NULL) {printf("內存分配失敗\n");return;}// 取前k個元素構建大堆for (int i = 0; i < k; i++)heap[i] = arr[i];build_max_heap(heap, k);// 遍歷剩余元素for (int i = k; i < n; i++){if (arr[i] < heap[0]){heap[0] = arr[i];max_heapify(heap, 0, k);}}// 排序結果并輸出heap_sort(heap, k);for (int i = 0; i < k; i++)result[i] = heap[i];free(heap);
}// 生成隨機數組
void generate_random_array(int* arr, int size, int min, int max)
{srand(time(NULL));for (int i = 0; i < size; i++){arr[i] = min + rand() % (max - min + 1);}
}// 打印數組
void print_array(int* arr, int size)
{for (int i = 0; i < size; i++){printf("%d ", arr[i]);if ((i + 1) % 10 == 0)printf("\n");}printf("\n");
}// 驗證結果正確性(通過全排序對比)
void verify_result(int* arr, int n, int k, int* topk)
{// 創建數組副本并排序int* copy = (int*)malloc(n * sizeof(int));for (int i = 0; i < n; i++)copy[i] = arr[i];heap_sort(copy, n);  // 注意:這里堆排序是升序printf("\n驗證結果(前10個最小元素):\n");printf("算法結果:");for (int i = 0; i < k; i++)printf("%d ", topk[i]);printf("\n正確結果:");for (int i = 0; i < k; i++)printf("%d ", copy[i]);printf("\n");// 檢查是否一致int correct = 1;for (int i = 0; i < k; i++){if (topk[i] != copy[i]){correct = 0;break;}}printf("驗證結果:%s\n", correct ? "正確" : "錯誤");free(copy);
}int main()
{const int N = 10000;  // 數據總量const int K = 10;     // 要找的最小元素個數int* arr = (int*)malloc(N * sizeof(int));int* topk = (int*)malloc(K * sizeof(int));// 生成10000個1到100000之間的隨機數generate_random_array(arr, N, 1, 100000);printf("已生成10000個隨機數\n");// 計算前10個最小元素clock_t start = clock();get_topk_smallest(arr, N, K, topk);clock_t end = clock();// 輸出結果printf("\n最小的前10個數(升序排列):\n");print_array(topk, K);// 輸出耗時double time_spent = (double)(end - start) / CLOCKS_PER_SEC;printf("計算耗時:%.6f秒\n", time_spent);// 驗證結果verify_result(arr, N, K, topk);// 釋放內存free(arr);free(topk);return 0;
}

運行結果:

結束語

經過上節堆的學習,這一節我們對于堆的Top K問題的學習與理解相對會輕松很多。

感謝您的三連支持!!!

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

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

相關文章

使用HTTPS 服務在瀏覽器端使用攝像頭的方式解析

1.方式1 // vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import basicSsl from vitejs/plugin-basic-sslexport default defineConfig({plugins: [vue(),basicSsl({name: test,domains: [192.168.15.166, localhost], // 添加您的IPc…

上下文管理器和異步I/O

目錄 一、上下文管理器 1.1 定義 1.2 特點 1.3 適用場景 1.4 具體實現 1.5 具體實例 1.5.1 文件管理器 1.5.2 線程鎖釋放資源 二、異步I/O 2.1 定義 2.2 特點 2.3 實現方式 2.4 適用場景 高并發網絡服務&#xff1a;Web服務器、API服務等需要處理大量并發連接 2…

LabVIEW信號監測與分析

借助 LabVIEW 平臺&#xff0c;生成含正弦波與噪聲的信號&#xff0c;經頻譜分析等處理&#xff0c;結合動態限值判斷信號是否超限&#xff0c;廣泛用于音頻、振動等領域的信號監測&#xff0c;助力高效開展信號分析與質量把控。概念說明系統圍繞信號的生成、處理、分析及監測展…

MySQL數據庫與表的創建、修改及數據操作指南

精選專欄鏈接 &#x1f517; MySQL技術筆記專欄Redis技術筆記專欄大模型搭建專欄Python學習筆記專欄深度學習算法專欄 歡迎訂閱&#xff0c;點贊&#xff0b;關注&#xff0c;每日精進1%&#xff0c;與百萬開發者共攀技術珠峰 更多內容持續更新中&#xff01;希望能給大家帶來…

?new species of flying reptile1 discovered in Scotland?

Pterosaur: new species of flying reptile1 discovered in Scotland 蘇格蘭斯凱島發現新翼龍物種 考古學家們在蘇格蘭斯凱島發現了一個新的翼龍物種。這種獨特的飛行爬行動物生活在1.68 – 1.66億年前。 This flying reptile soared over the heads of dinosaurs2 when Scotla…

03 節點行為

審批流程圖如下圖&#xff0c;在此流程圖中&#xff0c;存在兩個UserTask節點&#xff0c;第一個節點是主管審批&#xff0c;第二個節點是產品經理審批&#xff0c;兩個節點中間有一個排他網關&#xff0c;此網關用來對主管審批的結果進行判斷&#xff0c;如果主管審批通過&…

深度卷積生成對抗網絡詳解與實現

深度卷積生成對抗網絡詳解與實現 0. 前言 1. 網絡架構 1.1 批歸一化 1.2 激活 1.3 上采樣 2. 構建 DCGAN 2.1 生成器 2.2 判別器 2.3 訓練 DCGAN 0. 前言 深度卷積生成對抗網絡 (Deep Convolutional Generative Adversarial Network, DCGAN) 是基于生成對抗網絡 (Generative A…

CF607B Zuma -提高+/省選-

CF607B Zuma codeforces 原鏈接 題目描述 Genos\texttt{Genos}Genos 最近在他的手機上下載了祖瑪游戲。在祖瑪游戲里&#xff0c;存在 nnn 個一行的寶石&#xff0c;第 iii 個寶石的顏色是 CiC_iCi?。這個游戲的目標是盡快的消滅一行中所有的寶石。 在一秒鐘&#xff0c;Ge…

拆分了解HashMap的數據結構

文章目錄 前言 一、底層數據結構總覽 二、核心組成部分詳解 1. 數組&#xff08;哈希表&#xff09; 2. 節點&#xff08;Node&#xff09; 3. 紅黑樹&#xff08;TreeNode&#xff09; 三、哈希函數與索引計算 四、哈希沖突的解決 五、擴容機制 六、關鍵特性與注意事…

關于電腦連接不到5g的WiFi時的一些解決辦法

方法一、設備管理器重卸載驅動后&#xff0c;重裝驅動。方法二、打開控制面板 “控制面板\網絡和 Internet\網絡連接” &#xff08;親測有效&#xff09;點擊更改適配器配置右擊當前的WLAN屬性點擊配置選擇“高級” 802.11a/b/g 無線模式選項欄 值&#xff1a;6.的雙…

Mathtype公式批量編號一鍵設置公式居中編號右對齊

插件[ygtools] 批量編號一鍵設置公式居中編號右對齊 單欄/多欄均可https://wwon.lanzout.com/i0NRf35vyw8j 下載密碼8543

基于ssm的小橘子出行客戶體驗評價系統[SSM]-計算機畢業設計源碼+LW文檔

摘要&#xff1a;隨著出行行業的快速發展&#xff0c;客戶體驗評價對于出行服務質量的提升至關重要。本文設計并實現了基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架的小橘子出行客戶體驗評價系統。該系統涵蓋系統用戶管理、司機信息管理、客戶評價管理等功…

算法日記---二分查找

目錄 前言 一、二分查找 1.思想 2.簡單二分 3.優點 4.局限性 二、模板 1.基本模板 2.簡單例題&#xff08;LeetCode&#xff09; 4.有重復元素的二分 5.0-1問題 總結 前言 本文通過講解簡單的二分查找配合leetcode例題對二分查找本質、模板進行了基礎的總結 提示&a…

Level Set(水平集)算法——形象化講解

目錄 維度一&#xff1a;核心思想與比喻&#xff08;它像什么&#xff1f;&#xff09; 維度二&#xff1a;要解決什么問題&#xff1f;&#xff08;它能干嘛&#xff1f;有什么用&#xff1f;&#xff09; 維度三&#xff1a;工作原理&#xff08;它是怎么做到的&#xff1…

DDoS 攻防“軍備競賽”的幕后

談到 DDoS&#xff08;分布式拒絕服務攻擊&#xff09;&#xff0c;很多人會想到“黑客租用肉雞發流量&#xff0c;網站直接崩”。但事實上&#xff0c;如今的 DDoS 攻防早已變成一場 軍備競賽。攻擊者的武器越來越“工業化”&#xff1a;僵尸網絡商品化&#xff1a;黑市上&…

如何用 Rust 重寫 SQLite 數據庫(二):是否有市場空間?

用 Rust 實現一個類似 SQLite 的嵌入式數據庫非常有意義&#xff0c;但需要結合具體目標和場景來評估其價值。以下從技術、生態、市場需求和個人成長等多個維度展開分析&#xff0c;并給出結論。一、技術價值&#xff1a;Rust 與數據庫的天然契合 SQLite 作為全球裝機量最大的數…

【Web】ImaginaryCTF 2025 wp

目錄 imaginary-notes certificate codenames-1 passwordless pearl imaginary-notes I made a new note taking app using Supabase! Its so secure, I put my flag as the password to the "admin" account. I even put my anonymous key somewhere in the si…

oracel如何找到外鍵子表

要找到導致外鍵約束沖突的子表&#xff08;即包含"child record"的表&#xff09;&#xff0c;可以通過以下SQL查詢在Oracle數據庫中定位&#xff1a;1. 查詢約束基本信息&#xff08;確定父表和子表&#xff09;SELECT owner, constraint_name, table_name AS child…

智源研究院新研究:突破物理世界智能邊界的RoboBrain 2.0,將重構具身AI能力天花板

當你對著家用機器人說"把杯子放在筆筒和鍵盤之間&#xff0c;對齊杯身logo"時&#xff0c;它能精準理解空間關系并執行動作&#xff1b;當多臺機器人在超市協作補貨時&#xff0c;它們能自主規劃軌跡、避免沖突并完成長周期任務——這些曾經出現在科幻電影中的場景&a…

【2025】Office核心組件Microsoft word,Excel,PowerPoint詳細使用指南

Office 核心組件使用指南 Microsoft Word 文字處理 Word主要用于創建和編輯文檔&#xff0c;如信件、報告、論文等。 2025Office&#x1f517; 1. 界面認識 快速訪問工具欄&#xff1a;位于左上角&#xff0c;可自定義保存、撤銷、恢復等常用命令。功能區&#xff1a;頂部…