數據結構——Top-k問題

Top-k問題

  • 方法一:堆排序(升序)(時間復雜度O(N*logN))
    • 向上調整建堆(時間復雜度:O(N * logN) )
    • 向下調整建堆(時間復雜度:O(N) )
    • 堆排序代碼
  • 方法二:直接建堆法(時間復雜度:O(N+k×logN) ≈ O(N))
  • 方法三:建一個k個數的小堆(時間復雜度O(k+(N-k)×logk)≈O(N))

方法一:堆排序(升序)(時間復雜度O(N*logN))

升序堆排序的一般思路就是將給定的一組數據放在堆得數據結構當中去,然后進行不斷被的取堆頂元素放在數組當中,不斷地pop(刪除)。但是這種方法太麻煩了,自己還要手寫一個堆的數據結構以及一些接口函數,還要創建數組等,顯然不是最優解。
接上文的寫的兩種調整方式,向上調整和向下調整。 詳細見
思路:
①可以用向上調整或者向下對原數組進行調整,也就是建一個大堆(排升序大堆后面講為啥)
②接下來利用堆刪除的原理,將堆頂的數據和數組最后一個交換(也就是將堆頂和堆尾的數據進行交換),然后就相當于最大的數放在了最后一個位置,所以最后一個位置的數據確定了,接下來對剩下的數據進行向下調整,再重復以上操作。
在這里插入圖片描述

ps:排升序建大堆而不是小堆的原因,反證思路來看,若建小堆的話,最小的數據在第一個,第一個數據確定了,但是剩下的數據很暖再重新調整成為一個新的小堆的數據結構,所以排升序建小堆很難是實現

向上調整和向下調整都可以完成建堆的操作,但是他們的時間復雜度有所不同,接下來講一下他們的取舍。

向上調整建堆(時間復雜度:O(N * logN) )

for (int i = 1; i < n; i++){AdjustUp(a, i);}

因為堆是完全二叉樹,而滿二叉樹也是完全二叉樹,此處為了簡化使用滿二叉樹來證明(時間復雜度本來看的就是近似值,多幾個節點不影響最終結果):
在這里插入圖片描述

向下調整建堆(時間復雜度:O(N) )

for (int i = (n - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);//a是堆的數組,n是防止數組越界的數組數據個數,i是開始向下調整的位置}

向下調整建堆得思路:從第一個非葉子結點開始從數組得后面向前面一個一個進行調整
在這里插入圖片描述

復雜度證明:
在這里插入圖片描述

看到這里很容易發現向下調整方法建堆得時間復雜度更加合適

堆排序代碼

// 對數組進行堆排序
void HeapSort(int* a, int n)
{//思路:向上調整方法建堆建堆//>這里排升序要建大堆,因為建小堆的話,接下來排升序第一個數據好處理,//但是剩下的數據重新排成堆的話關系全都亂了//所以這時候建大堆,和刪除的思路一樣,首先交換堆頂和堆尾,這時候最大的數據放到了最后一個位置//然后將前面n-1個數據看成新的堆進行向下調整,然后再找次大的數放在倒數第二的位置//建堆有兩種方式 向上調整建堆和向下調整建堆,時間復雜度分別為O(N*logN)和O(N)//向上調整建堆 建堆時間復雜度 O(N*logN)/*for (int i = 1; i < n; i++){AdjustUp(a, i);}*///向下調整建堆  時間復雜度為O(N)//向下調整的條件是調整節點的左右子樹都為大堆或者小堆//所以從下邊開始進行向下調整(大堆),這時候大的數會慢慢上浮,最先調整的位置不能是葉子節點,那樣會重復很多操作//應該從最后一個父親節點開始進行向下調整 最后一個父親節點的下標為 (n-1)/2,然后按數組下標的順序遞減進行調整for (int i = (n - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);//a是堆的數組,n是防止數組越界的數組數據個數,i是開始向下調整的位置}while (--n)  //排序的時間復雜度為O(N*logN){//此處為--n的原因:一共有n個數據,循環一次確定一個數據的位置,循環n-1次之后就可以確定n-1個數據的位置,// 前面已經確定了n-1個位置,最后一個數據的位置也已經確定,所以當n=0的時候的那一次循環可以不需要進行就可以// //此時n已經自減1,所以此時n就為堆尾數據,且n為下一個新堆的數據個數,所以后面向下調整直接傳參nSwap(&a[0], &a[n]);//交換堆頂和堆尾的數據AdjustDown(a, n, 0);//n為新堆的數據個數}//總結:堆排序的時間復雜度為O(N*logN),因為堆排序有兩個步驟①建堆②排序//建堆向上調整建堆的時間復雜度為O(N*logN),向下調整建堆的時間復雜度為O(N),相對較快,//但是排序的時間復雜度都為O(N*logN),所以決定了堆排序的時間復雜度為O(N*logN)。
}

方法二:直接建堆法(時間復雜度:O(N+k×logN) ≈ O(N))

思路:有了堆排序中的向下調整建堆法,可以將這N個數建一個小堆,然后取堆頂元素打印,然后Pop(刪除)k次這樣稍微簡單些,但是當數據個數N太大得時候,對內存得要求很大,會占用很多內存,因為這種方法中,需要在堆區中創建一個N個數據的動態1數組,然后將數據放在數組當中去,因為如果在N很大的情況下,有可能你的設備內存并沒有那么大。所以這是這種方法的缺點。
一般數據都是放在磁盤或者文件中,所以我接口函數可以傳文件流

//創建隨機n個數據
void CreatData(int n)
{int k = 10;srand((unsigned int)time(NULL));//調用rand()的返回值形成隨機數必須調用srand函數//srand函數的形參為unsigned int類型的,且形參為不斷變化的數才可以生成為隨機數//所以形參傳參為時間戳函數time,time函數的形參得傳NULLconst char* file = "data.txt";FILE* pf = fopen(file, "w");if (pf == NULL){perror("fopen fail\n");return;}else{printf("打開成功\n");}for (int i = 0; i < n; i++){fprintf(pf, "%d\n", rand() % 10000);}fclose(pf);//關閉文件pf = NULL;
}//打印n個數據中最大的k個數據
void PrintTop1(const char* file, int k, int n)//n是數據個數
{FILE* pf = fopen(file, "r");if (pf == NULL){perror("PrintTop fopen fail");return;}//1.把n個數建大堆int* top = (int*)malloc(sizeof(int) * n);//創建一個動態數組存放堆的數據if (top == NULL){perror("top malloc fail\n");return;}//讀取n個數放在堆當中去for (int i = 0; i < n; i++){fscanf(pf, "%d", &top[i]);}//向下調整建大堆for (int i = (n - 1) / 2; i >= 0; i--){AdjustDown(top, n, i);}//打印 k次堆頂元素for (int i = 0; i < k; i++){printf("%d ", top[0]);//打印堆頂元素Swap(&top[0], &top[n - i]);AdjustDown(top, n - i - 1, 0);//這里交換后的那個數據不能算入內}
}
//測試CreatData(10000000);//創建數據
PrintTop1("data.txt", 10,10000000);//這里測試時候可以直接只改PrintTop1中n的大小,因為前面CreatData創建數據會花費很多時間,導致第二個函數并不能直接運行//CreatData(10000000);//創建數據
//PrintTop1("data.txt", 10, 100000000);//CreatData(10000000);//創建數據
//PrintTop1("data.txt", 10, 1000000000);

代碼先放在這里,接下來我來驗證:
在這里插入圖片描述
所以這種方法當數據N很大的時候并不可取

方法三:建一個k個數的小堆(時間復雜度O(k+(N-k)×logk)≈O(N))

思路:取前k個數建立一個k個數的小堆,然后遍歷剩下的所有數據,并和堆頂進行比較,只要比堆頂大就和堆頂交換,然后進行調整,然后進行循環,遍歷結束之后也就證明這k個數為所要求的k個數。例如:
在這里插入圖片描述
代碼:

//小堆的向下調整   和大堆的向下調整一樣
void AdjustDownSmall(HPDateType* a, int n, int parent)//向下調整(小堆)  時間復雜度O(logN)
//向下調整的條件是調整節點的左右子樹都為大堆或者小堆
//思路為從下標為parent位置開始向下的孩子節點不斷比較進行調整,直到最后一個數據,
//所以需要傳參堆的有效數據個數n
{int leftchild = parent * 2 + 1;while (leftchild < n)//{int rightchild = parent * 2 + 2;if (rightchild < n && a[leftchild] > a[rightchild]){//判斷一下該父親節點是否有右孩子,防止數組越界//默認左孩子小于右孩子//若右孩子小于左孩子則交換下標Swap(&leftchild, &rightchild);}if (a[parent] > a[leftchild]){Swap(&a[parent], &a[leftchild]);//若不符合堆的要求則交換parent = leftchild;//將原父親數據對應下標也賦值過來leftchild = parent * 2 + 1;//新的孩子的下標}else//若符合堆的要求就退出循環{break;}}
}//Top-k 問題  取前k個較大的數
void PrintTop(const char* file,  int k)//把文件傳進來和需要找的前k個數
{FILE* pf = fopen(file, "r");if (pf == NULL){perror("PrintTop fopen fail");return ;}//1.把前k個數建小堆int* top = (int*)malloc(sizeof(int) * k);//創建一個動態數組存放堆的數據if (top == NULL){perror("top malloc fail\n");return;}//從文件中讀取k個數據放在數組中for (int i = 0; i < k; i++){fscanf(pf, "%d", &top[i]);}for (int i = (k - 1) / 2; i >= 0; i--){AdjustDownSmall(top, k, i);}//2.遍歷剩下的n-k個數并與堆頂作比較,若比堆頂大則交換然后再進行向下調整int val = 0;int ret = fscanf(pf, "%d", &val);while (ret != EOF){if (val > top[0]){Swap(&val, &top[0]);AdjustDownSmall(top, k, 0);}ret = fscanf(pf, "%d", &val);}//打印數組for (int i = 0; i < k; i++){printf("%d\n", *(top+i));//printf("%d\n", top[i]);//會報錯C6385//像數組一樣在連續內存空間存儲的多個數據才使用下標法//這種應該是編譯器問題 具體不清楚}free(top);top = NULL;fclose(pf);pf = NULL;
}

分析:對比時間復雜度方法二和方法三的時間復雜度都差不多,方法三在N為很大的情況下,所用內存空間是取決于k的,因為k一般是一個很小的數一般不會很大,導致內存崩潰。
在這里插入圖片描述

方法二 方法三自己實測其實時間上,對于1000 0000個數據的時候運行的時候都會大約等待個5 6秒,所以他們的時間復雜度大差不差,優勢在于空間的使用。

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

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

相關文章

LeetCode---386周賽

題目列表 3046. 分割數組 3047. 求交集區域內的最大正方形面積 3048. 標記所有下標的最早秒數 I 3049. 標記所有下標的最早秒數 II 一、分割數組 這題簡單的思維題&#xff0c;要想將數組分為兩個數組&#xff0c;且分出的兩個數組中數字不會重復&#xff0c;很顯然一個數…

Redis 的哨兵模式配置

1.配置 vim sentinel.conf# mymaster 給主機起的名字 # 192.168.205.128 主機的ip地址 # 6379 端口號 # 2 當幾個哨兵發現主觀宕機&#xff0c;則判定為客觀宕機。 原則上是大于一半。比如三個哨兵&#xff0c;則設置為 2 sentinel monitor mymaster 192.168.205.128 63…

【動態規劃入門】01背包問題

每日一道算法題之01背包問題 一、題目描述二、思路三、C++代碼四、結語一、題目描述 題目來源:Acwing 有N件物品和一個容量是 V的背包。每件物品只能使用一次。第 i件物品的體積是 vi,價值是 wi。 求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大…

LeetCode題練習與總結:合并K個升序鏈表

一、題目 給你一個鏈表數組&#xff0c;每個鏈表都已經按升序排列。 請你將所有鏈表合并到一個升序鏈表中&#xff0c;返回合并后的鏈表。 二、解題思路 創建一個最小堆&#xff08;優先隊列&#xff09;來存儲所有鏈表的頭節點。這樣我們可以始終取出當前所有鏈表中值最小…

人工智能指數報告2023

人工智能指數報告2023 主要要點第 1 章 研究與開發第 2 章 技術性能第 3 章 人工智能技術倫理第 4 章 經濟第 5 章 教育第 6 章 政策與治理第 7 章 多樣性第 8 章 輿論 人工智能指數是斯坦福大學以人為本的人工智能研究所&#xff08;HAI&#xff09;的一項獨立倡議&#xff0c…

Java 石頭剪刀布小游戲

一、任務 編寫一個剪刀石頭布游戲的程序。程序啟動后會隨機生成1~3的隨機數&#xff0c;分別代表剪刀、石頭和布&#xff0c;玩家通過鍵盤輸入剪刀、石頭和布與電腦進行5輪的游戲&#xff0c;贏的次數多的一方為贏家。若五局皆為平局&#xff0c;則最終結果判為平局。 二、實…

redis 為什么會阻塞

目錄 前言 客戶端交換時的阻塞 redis 磁盤交換的阻塞 主從節點交互的阻塞 切片集群交互時的阻塞 異步執行的演變 redis 異步執行如何實現的 前言 大家對redis 比較熟悉吧&#xff0c;只要做項目都會用到redis&#xff0c;提高系統的吞吐。小米商城搶購高峰18k的qps&…

KubeSphere平臺安裝系列之三【Linux多節點部署KubeSphere】(3/3)

**《KubeSphere平臺安裝系列》** 【Kubernetes上安裝KubeSphere&#xff08;親測–實操完整版&#xff09;】&#xff08;1/3&#xff09; 【Linux單節點部署KubeSphere】&#xff08;2/3&#xff09; 【Linux多節點部署KubeSphere】&#xff08;3/3&#xff09; **《KubeS…

一句話講清楚數據庫中事務的隔離級別(通俗易懂版)

為什么我只說通俗易懂版不說嚴謹版? 因為嚴謹版遍地都是, 但是他們卻有一個缺點就是讓人看得云里霧里, 所以這就是我寫通俗易懂版的初衷! 但是既然是通俗易懂版就必然有缺陷, 只為了各位在開發過程中頭腦更加清晰, 如有錯誤還望兄弟們不吝賜教! 在MySQL數據庫中,事務一共有4…

C語言之strcmp函數,strlen函數

strcmp函數是比較兩個字符串ASCII大小的函數。 比較方式是自左向右比較&#xff0c;直到出現不同字符或者\0為止 語法格式 strcmp(字符串1,字符串2&#xff09; 如果兩個字符串相同&#xff0c;會返回數值0 如果字符串1>字符串2,會返回一個正數 如果字符串1<字符串2…

新一代電話機器人開源PHP源代碼

使用easyswoole 框架開發的 新一代電話機器人開源PHP源碼 項目地址&#xff1a;https://gitee.com/ddrjcode/robotphp 代理商頁面演示地址 http://119.23.229.15:8080 用戶名&#xff1a;c0508 密碼&#xff1a;123456 包含 AI外呼管理&#xff0c;話術管理&#xff0c;CR…

每日一題 — 復寫零

1089. 復寫零 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 首先找到最后一個復寫的數&#xff1a; 雙指針算法&#xff1a; 1、先判斷 cur 位置上的值 2、然后決定 dest 移動一步還是兩步 3、然后判斷 dest 是否到終點了 4、最后 cur 處理越界的情況 arr[n-1] …

使用sourceCompatibility = 11不匹配解決方法

運行springbootgradle項目報錯。 原因&#xff1a;在生產該項目時&#xff0c;選擇的JDK是11版本的&#xff0c;但是本地電腦只安裝了1.8版本。不兼容所以報錯。 解決辦法&#xff1a; 找到build.gradle配置文件—>找到sourceCompatibility ‘11’—>把11改成自己本地…

思維題(藍橋杯 填空題 C++)

目錄 題目一&#xff1a; ?編輯 代碼&#xff1a; 題目二&#xff1a; 代碼&#xff1a; 題目三&#xff1a; 代碼&#xff1a; 題目四&#xff1a; 代碼&#xff1a; 題目五&#xff1a; 代碼&#xff1a; 題目六&#xff1a; 代碼七&#xff1a; 題目八&#x…

用python和pygame庫實現刮刮樂游戲

用python和pygame庫實現刮刮樂游戲 首先&#xff0c;確保你已經安裝了pygame庫。如果沒有安裝&#xff0c;可以通過以下命令安裝&#xff1a; pip install pygame 示例有兩個。 一、簡單刮刮樂游戲 準備兩張圖片&#xff0c;一張作為背景bottom_image.png&#xff0c;一張作…

Leetcoder Day35| 動態規劃part02

62.不同路徑 一個機器人位于一個 m x n 網格的左上角 &#xff08;起始點在下圖中標記為 “Start” &#xff09;。 機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角&#xff08;在下圖中標記為 “Finish” &#xff09;。 問總共有多少條不同的路徑&#xff…

如何在Linux配置C、C++、Go語言的編譯環境?

在 Linux 系統上配置 C、C、Go 語言的編譯環境可以通過安裝相應的編譯器和相關工具來實現。以下是在 Linux 系統上配置這些語言的編譯環境的一般步驟&#xff1a; 1. C 和 C 編譯環境配置&#xff1a; 安裝 GCC 編譯器&#xff08;一般 Linux 發行版都會包含&#xff09;&…

Android 顯示系統框架

一.FrameBuffer FrameBuffer 介紹&#xff1a; FrameBuffer中文譯名為幀緩沖驅動&#xff0c;它是出現在2.2.xx內核中的一種驅動程序接口。主設備號為29&#xff0c;次設備號遞增。 Linux抽象出FrameBuffer這個設備來供用戶態進程實現直接寫屏。FrameBuffer機制模仿顯卡的功能…

Day11:信息打點-Web應用企業產權指紋識別域名資產網絡空間威脅情報

目錄 Web信息收集工具 業務資產-應用類型分類 Web單域名獲取-接口查詢 Web子域名獲取-解析枚舉 Web架構資產-平臺指紋識別 思維導圖 章節知識點&#xff1a; Web&#xff1a;語言/CMS/中間件/數據庫/系統/WAF等 系統&#xff1a;操作系統/端口服務/網絡環境/防火墻等 應用…

dart中的事件隊列與微任務

dart在每個事件循環中&#xff0c;會先執行同步任務代碼&#xff0c;然后分別檢查兩個任務隊列&#xff1a;微任務隊列和事件隊列。dart總是先執行微任務隊列中的代碼&#xff0c;然后才是事件隊列中的代碼。當兩個隊列中的任務都執行完成后&#xff0c;線程進入休眠狀態&#…