【追求卓越12】算法--堆排序

引導

????????前面幾節,我們介紹了有關樹的數據結構,我們繼續來介紹一種樹結構——堆。堆的應用場景有很多,比如從大量數據中找出top n的數據;根據優先級處理網絡請求;這些情景都可以使用堆數據結構來實現。

什么是堆?

正如上文所說,堆是一種樹結構。它的定義滿足以下兩點:

  1. 堆是一個完全二叉樹
  2. 堆中每個節點的值必須大于等于(或小于等于)其子樹中每個節點的值

完全二叉樹在前面我們已經講過;第二點,其實等價于堆中每個節點大于等于(或小于等于)其左右子節點。

對于每個節點大于等于左右子節點,我們稱為大頂堆;每個節點小于等于左右子節點,我們稱為小頂堆。

堆的操作

堆這種數據結構有點不一樣。不適合查找或刪除指定值的操作。

但是它可以幫助我們解決一些特定的問題。但是不同的問題,都離不開以下核心操作:插入一個元素,刪除堆頂元素。

創建一個堆

我們知道完全二叉樹適合用數組進行保存。因為它不需要保存左右指針,通過數組下標就可以實現。

數組下表為i的節點,其左子節點的數組下標為2*i,右子節點的數組下標為 2 *i+1;任意節點的父節點下標為i/2;根節點的下標為1.

插入一個元素

????????向堆中插入一個數據,我們可以將新加入的數據加入到堆的后面,使其繼續維持一個完全二叉樹。

????????但是插入一個新的數據就無法保證其父節點大于等于(或小于等于)左右子節點了。于是我們還要進行調整,使其滿足堆的特性。我們稱為這個過程為堆化。堆化的過程,其實也簡單,只要循著插入節點,向上對比,進行交換即可。

/
*
大頂堆*
/
#define MAXLEN 1024
int heap[MAXLEN+1] = 0;
int count = 0;
int insert(int data)
{
??? if(count >= MAXLEN)
??????? return -1; /
*堆滿了*
/

??? count++;

??? heap[count] = data;
??? int i = count;
??? int temp = 0;
??? while(i/2 > 0 && heap[i] > heap[i/2])
??? {
??????? temp = heap[i/2];
??????? heap[i/2] = heap[i];
??????? heap[i] = temp;

??????? i = i/2;
??? }
}

刪除堆頂元素

由堆的特性所致,刪除堆頂元素,實際上就是找出最大或者最小值的過程。刪除堆頂元素其實也簡單,可按照以下思路進行:

  1. 將最后一個元素放到堆頂,保證樹是一個完全二叉樹
  2. 從堆頂開始進行堆化,使其滿足特質二

/
*
大頂堆*
/
#define MAXLEN 1024
int heap[MAXLEN+1] = 0;
int count = 0;
int? removeTop()
{
??? if(count == 0) return -1;
??? heap[1] = a[count];
??? count--;

??? heapify(count,1);
??? return 0;
}
/
*從某個節點開始,向下堆化*
/
int heapify(int count, int start)
{
??? while(true)
??? {
??????? int max = start;
??????? if(i
*2 < count && heap[i*
2]>heap[max])
??????????? max = i
2;
*??????? if(i*
2+1 < count && heap[i
2+1]>heap[max])
*??????????? max = i*
2+1;???

??????? if(max == start)
??????????? break;
??????? swap(heap,max,start);/
*交換max下標和start下標的值*
/
??????? start = max;
??? }
}

????????通過插入一個數據和刪除堆頂元素的操作流程,我們知道時間的消耗主要集中在堆化中。由于完全二叉樹的樹高不會超過logn。所以堆的插入數據和刪除堆頂元素的時間復雜度都是O(logn)。

如何實現堆排序?

????????我們已經知道堆的性質以及基本的操作。但是對于一組數據我們該如何進行堆排序呢?原始的數據肯定是無序也不是堆結構的。因此我們第一步應該是建立堆。

建堆

建堆的方式有兩種

第一種最簡單:首先假設堆中的數據為0,依次將原始數據放到堆中。我們知道插入數據的時間復雜度是O(logn)。故插入第一個數據的消耗的時間為log1,第二個數據消耗的時間為log2...以此推測,第n個數據消耗時間為logn。

方法一消耗的時間為:log1+log2+log3...+logn=O(logn!),并且需要額外的內存。

第二種思路就比較特殊:對于完全二叉樹而言,下標從n/2 + 1到n的節點都是葉子節點,并且葉子節點是不需要堆化的。

因此我們只需要將原始數據中下標從1到n/2進行堆化即可,并且不需要額外的內存。

int buildheap(int heap[],int count)
{
??? int i = n/2;
??? for( ; i >= 1 ; i--)
??????? heapify(heap,count,i);
}

那么第二種思路的時間復雜度是多少呢?

我們知道堆化的操作和所在節點的高度有關。由于葉子節點不需要堆化,并且每層節點和節點高度成正比,如圖:

故消耗的時間為:

T = 2^0
*h + 2^1*
(h-1) + 2^2*(h-2) +...+ 2^(h-1)*1
T = -h +2 + 2^2 + 2^3 + ... + 2^h
T=2^(h+1) -h - 2

由于h=logn(以2為底)通過化簡,得出時間復雜度為O(2n-h-2)=O(n)。

至此,我們已將完成建堆

排序

在刪除堆頂元素時,我就說過了,這就是排序的過程。因此堆排序,就是將堆頂元素不斷刪除,直至堆為空。

代碼如下:

int heapsort()
{
??? buildheap(heap,count);
??? int k = count;
??? while(k > 1)
??? {
??????? printf("%d\r",heap[1]);
??????? swap(heap,1,count);
??????? k--;
??????? heapify(a,k,1);
??? }
??? printf("\n");
}

現在我們看堆排序的時間復雜度是多少?

????????建堆的時間復雜度是O(n),排序的時間復雜度是O(nlogn)(實際上應該是logn+log(n-1)+log(n-2)+...+log(1),這里方便,我們記錄為O(logn))。故堆排序的時間復雜度為O(nlogn)。

快速排序的性能為什么比堆排序的要好?

在排序章節,我們提到了快速排序,它是時間復雜度也是O(nlogn)。但是在實際開發過程中,我們快速排序的性能優于堆排序。我覺得原因有以下幾點:

  1. 堆排序的訪問沒有快速排序好。即使堆排序也是以數組為存儲。但是由于它訪問不是連續的,不能很好的利用CPU緩存。
  2. 對于同樣的數據,堆排序的數據交換要多于快速排序。

堆應用

優先級隊列

????????在工作中,我們經常會遇到定時任務的問題。一般思路:將每個任務保存到數組中,每過一個時間間隔(1秒),就檢測一下數組,看哪個任務達到了設定時間,如果到達了就取出任務執行,并刪除

其實這樣的定時器效率是很低的,為什么呢?

  1. 往往到達下一個任務之間的間隔是需要很長時間,那么在達到之前所有的檢測都是無用的,這是在浪費系統資源
  2. 如果任務列表的長度很大,那么每次遍歷,都會消耗很多的資源。這也是不可取的。

針對該類問題,我們可以采用優先級隊列來解決。我們按照任務時間進行堆化。堆頂就是最先要被執行的任務。

修改方案:

  1. 根據任務時間進行堆化(小堆頂),堆頂任務就是下一個將要被執行的任務
  2. 計算現在距離下一個任務的時間T,sleep T秒,再執行堆頂任務。這樣在執行下一個任務之前,定時器不需要做任何事情,性能大大提高。

Top k

堆在求top k的問題中,表現的也同樣出色。

求top K的問題,我們可以先將數據排序,再取前K個元素。我們可以通過快排,那么求top K的時間復雜度是O(nlogn),這同樣也很快,難道堆會更好嗎?

堆應用的思路:

  1. 取數據中的前K個元素,建立小頂堆。
  2. 繼續遍歷數組中的元素,如果元素大于堆頂元素,則加入堆中,并刪除堆頂元素。若比堆頂元素小,則不進行處理。
  3. 當遍歷完整個數組后,堆中的數據就是前top K數據。

假設每個元素都需要進行插入,那么就是堆化n個元素,堆化的時間復雜度是O(logK),故使用過堆求top K的問題,其時間復雜度是O(nlogk)。

求中位數

在求中位數的問題中,我們一般的思路是將原始數據進行排序。

再求中位數,如果n為奇數的話,中位數就是n/2+1;如果n為偶數的話,中位數就是n/2或n/2+1。這種方式的消耗主要集中在對n個數據排序上面,使用快速排序,它的時間復雜度就是O(nlogn)。

但這樣的方式比較適合靜態數據,當數據能夠動態添加時,再采取這樣的方式,就不再適合了。

堆對于這種動態數據,求中位數有其優勢。我們的思路是這樣的:

  1. 先將現有數據進行升序排序,取前n/2個數據進行大堆頂堆化。后n/2個數據進行小堆頂堆化。(假設n為偶數。若n為奇數,取前n/2+1個數據為大堆頂)
  2. 當n為偶數時,中位數就是大堆頂和小堆頂的堆頂元素;當n為奇數時,中位數就是大堆頂的堆頂元素;
  3. 插入一個元素時,如果插入元素小于等于大堆頂元素,我們就將元素插入到大堆頂;反之插入到小堆頂。這樣就容易不滿足我們的預定:取前n/2個數據進行大堆頂堆化。后n/2個數據進行小堆頂堆化。(假設n為偶數。若n為奇數,取前n/2+1個數據為大堆頂)。我們就需要將兩個堆的堆頂元素互相調整,使其滿足上訴約定

????????通過上述的邏輯,我們就可以得到一個適合動態數據,求中位數的方案。其時間復雜度主要集中在堆化過程O(logn)。

總結

????????本節,我們介紹了堆這種數據結構以及堆結構的定義。也從代碼的角度去實現了堆和堆排序。

????????最后也比較了快速排序相對于堆排序的優點。我后面會繼續總結一個關于堆排序的應用方向的一系列題。爭取吃透。

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

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

相關文章

【20年揚大真題】編寫程序,功能是計算1~10之間的偶數之和

【20年揚大真題】 編寫程序&#xff0c;功能是計算1~10之間的偶數之和 #include<stdio.h> int main() {int i 1;int sum 0;for (i 1;i < 10;i){if (i % 2 0){sum i;}}printf("%d", sum); }

Java核心知識點整理大全9-筆記

目錄 null文章瀏覽閱讀9w次&#xff0c;點贊7次&#xff0c;收藏7次。Java核心知識點整理大全https://blog.csdn.net/lzy302810/article/details/132202699?spm1001.2014.3001.5501 Java核心知識點整理大全2-筆記_希斯奎的博客-CSDN博客 Java核心知識點整理大全3-筆記_希斯…

FindMy技術用于充電寶

充電寶是一種便捷的充電器&#xff0c;方便個人隨身攜帶&#xff0c;能夠自行儲備電能&#xff0c;為主流電子設備提供充電服務。它廣泛應用于沒有外部電源供應的場所&#xff0c;例如旅行、戶外活動或緊急情況下&#xff0c;為用戶的手持設備提供持續的電力支持&#xff0c;確…

spring boot加mybatis puls實現,在新增/修改時,對某些字段進行處理,使用的@TableField()或者AOP @Before

1.先說場景&#xff0c;在對mysql數據庫表數據插入或者更新時都得記錄時間和用戶id 傳統實現有點繁瑣&#xff0c;這里還可以封裝一下公共方法。 2.解決方法&#xff1a; 2.1&#xff1a;使用aop切面編程&#xff08;記錄一下&#xff0c;有時間再攻克&#xff09;。 2.1.1&am…

讀書筆記:彼得·德魯克《認識管理》第30章 管理溝通

一、章節內容概述 我們知道&#xff0c;組織中的溝通是感知&#xff0c;也是期望&#xff0c;會產生要求&#xff0c;并且與信息不同&#xff0c;二者是對立的卻相互依賴。 我們知道&#xff0c;下行溝通沒有效果&#xff0c;只有上行溝通才能達到目的&#xff0c;并且 我們還…

軟件工程第十二周

軟件作坊、軟件危機、軟件過程控制、重型控制、敏捷、DevOps 這些術語概括了軟件開發歷史和實踐中的幾個重要概念和階段。讓我們逐一解析它們&#xff1a; 軟件作坊&#xff08;Software Craftsmanship&#xff09;&#xff1a;這是軟件開發的早期模式&#xff0c;強調個人技能…

【面試題】for...in 和 for...of 的區別

給大家推薦一個實用面試題庫 1、前端面試題庫 &#xff08;面試必備&#xff09; 推薦&#xff1a;★★★★★ 地址&#xff1a;web前端面試題庫 JavaScript 是一門強大而靈活的編程語言&#xff0c;提供了多種迭代對象的方式。兩個常見的迭代方式是 for...in 和…

Boost獲取當前時間并格式化為字符串

格式化為字符串 時間轉字符串有兩種方法 #include <boost/date_time/posix_time/posix_time.hpp> #include <iostream>std::string getCurrentTime() {boost::posix_time::ptime currentTime boost::posix_time::microsec_clock::local_time(); std::string …

centos 安裝k8s教程(一鍵安裝k8s)

第一步 準備幾臺機器 第二步 K8s Manager 服務器中添加docker支持 安裝教程請查看這個博客 docker 安裝詳細教程 點我 第三步安裝 KuboardSpray 教程在這里 第四步 下載k8s資源包 第五步 安裝k8s 點擊安裝后 顯示如下&#xff1a;等待完成

arduino入門一:點亮第一個led

void setup() { pinMode(12, OUTPUT);//12引腳設置為輸出模式 } void loop() { digitalWrite(12, HIGH);//設置12引腳為高電平 delay(1000);//延遲1000毫秒&#xff08;1秒&#xff09; digitalWrite(12, LOW);//設置12引腳為低電平 delay(1000); }

電腦桌面便簽工具選擇哪一款?

隨著互聯網時代的不斷發展&#xff0c;電腦成為日常工作及辦公中必不可少的工具&#xff0c;通過電腦這款工具&#xff0c;大家可以更好的進行工作、學習等方面的交流&#xff1b;電腦桌面便簽由于可以為大家整合一些工作及學習方面的備忘事項及筆記等&#xff0c;因而深受大家…

獲取驗證碼在倒計時未完成前清除驗證碼

場景&#xff1a; 在點擊獲取驗證碼后&#xff0c;驗證碼開始倒計時&#xff0c;在點擊登錄后&#xff0c;出現彈窗不跳轉頁面。因此在出現彈窗后&#xff0c;即使倒計時沒有結束&#xff0c;也要將倒計時的文字變為重新獲取驗證碼。 template代碼 <div class"form-b…

【Vue】Node.js的下載安裝與配置

目錄 一.下載安裝 官網&#xff1a; 二.環境變量的配置 三.設置全局路徑和緩存路徑 四.配置淘寶鏡像 五.查看配置 六.使用npm安裝cnpm ? 一.下載安裝 官網&#xff1a; https://nodejs.org/en/download 下載完之后&#xff0c;安裝的時候一直點next即可&#xff0c…

FlinkCDC實現主數據與各業務系統數據的一致性(瀚高、TIDB)

文章末尾附有flinkcdc對應瀚高數據庫flink-cdc-connector代碼下載地址 1、業務需求 目前項目有主數據系統和N個業務系統,為保障“一數一源”,各業務系統表涉及到主數據系統的字段都需用主數據系統表中的字段進行實時覆蓋,這里以某個業務系統的一張表舉例說明:業務系統表Ta…

BQL是什么如何使用?

BQL是什么如何使用&#xff1f; BQL來源于Business Query Language &#xff0c;是一種業務查詢語言。是北京碩迪制信科技有限公司根據以往統計分析案例研發的一種語言。特點是通過可視化界面對業務語言進行查詢、聚合、排序等操作&#xff0c;通過BQL引擎轉換為數據庫可執行的…

CSGO游戲搬磚市場下跌分析,是跑還是入?

CSGO市場下跌分析&#xff0c;是跑還是入&#xff1f; 以下所有都是阿陽本人最近幾年觀察市場和踩坑的一點經驗&#xff0c;由于篇幅不長所以肯定會很淺薄&#xff0c;大伙下嘴輕點 。 首先現在真的是CSGO市場最低點嗎&#xff1f;后續還會跌嗎&#xff1f;我們究竟是該繼續觀…

Course1-Week1:機器學習簡介

Course1-Week1&#xff1a;機器學習簡介 文章目錄 Course1-Week1&#xff1a;機器學習簡介1. 課程簡介1.1 課程大綱1.2 Optional Lab的使用 (Jupyter Notebooks)1.3 歡迎參加《機器學習》課程 2. 機器學習簡介2.1 機器學習定義2.2 有監督學習2.3 無監督學習 3. 線性回歸模型3.1…

golang學習筆記——使用映射

文章目錄 使用映射聲明和初始化映射添加項訪問項刪除項映射中的循環 使用映射 Go 中的映射是一個哈希表&#xff0c;是鍵值對的集合。 映射中所有的鍵都必須具有相同的類型&#xff0c;它們的值也是如此。 不過&#xff0c;可對鍵和值使用不同的類型。 例如&#xff0c;鍵可以…

Apach Ozone部署

前言 最近由于工作需要&#xff0c;要部署一套ozone。我自己對hadoop這套體系不是很熟悉&#xff0c;所以過程磕磕碰碰&#xff0c;好不容易勉強搭起來&#xff0c;所以記錄一下部署方式 準備 三臺主機&#xff0c;主機均已安裝jdk、hdfs&#xff0c;相關的安裝配置就不另外寫…

python二叉樹鏈樹_樹的鏈式存儲結構

二叉鏈樹是一種樹狀數據結構&#xff0c;其中每個節點最多有兩個子節點&#xff0c;分別稱為左子節點和右子節點。每個節點包含一個數據元素和指向其左右子節點的指針。二叉鏈樹可以是空樹&#xff0c;也可以是具有以下特點的非空樹&#xff1a; 1. 每個節點最多有兩個子節點。…