堆的實現及應用(優先順序佇列,堆排,TopK問題)
堆資料結構是一種陣列物件,它可以被看做是一棵完全二叉樹。
堆的二叉樹儲存有兩種方式:
1.最大堆:每個父節點的值都大於孩子節點
2.最小堆:每個父節點的值都小於小子節點
如上圖所示就是一個最小堆。
關於堆,其實說到底就是兩種演算法,一種是向下調整演算法,一種是向上調整演算法。我們先結合圖來分析一下這兩種演算法。
就拿上圖來說,上圖是一個小堆,接下來比如說我們要往裡插入一個9,那麼因為堆的底層實際上是一個數組物件,實際上就是一個vector,那麼插入的9,就是在如圖的位置
但因為這是個最小堆,所以要對9進行向上調整,把9和它的父親的值16比,因為9比16小,那麼就交換9和16的位置;再拿9和11比,9比11小,交換9和11的位置;9和10比,9比10小,交換9和10的位置。直到交換到9比它的父節點大了或是9已經交換到根節點了就停止,上圖在進行向上調整後的結果如下圖。
在進行向上調整的過程中,只會對插入位置的祖先這一條線產生影響,對堆的其他線並不會產生影響。向下調整也是如此,比如我們在進行刪除操作的時候,Pop掉的都是堆頂位置Top的值,也就是root位置的值(如向上調整好的圖就是Pop 9),我們一般採用的方法就是,先交換9和16的位置,再對該堆進行一個Pop操作(因為底層是一個vector,Pop操作直接刪除尾部元素),這樣一來9就刪除了
然而Top位置的值此時是16,所以就要對16進行向下調整,將16的兩個孩子的值進行比較,這裡因為是小堆,所以就選小的那個(大堆就選大的那個),所以就將16和10 的位置進行交換,以此類推,接著就是16和11交換,直到孩子都比16的值大,或是16已經是葉子節點了就停止。調整好的就是一開始的那個小堆
核心的演算法我們已經掌握了,接下來就是程式碼的實現了。因為應用的過程中,可能既會用到大堆也會用到小堆,同時寫一個大堆和一個小堆會產生程式碼重複,所以我們採用以下的方法來構建堆物件,這裡用到仿函式。
#pragma once
#include <iostream>
#include <vector>
using namespace std;
#include <assert.h>
template <class T>
struct Less//小堆
{
bool operator()(const T& left, const T& right)
{
return left < right;
}
};
template <class T>
struct Greater//大堆
{
bool operator()(const T& left, const T& right)
{
return left > right;
}
};
template <class T,class Compare=Greater<T>>//預設構建大堆
class Heap
{
public:
Heap()
{}
Heap(const T* a, size_t size)
{
assert(a);
_a.reserve(size);
for (size_t i = 0; i < size; ++i)
{
_a.push_back(a[i]);
}
for (int i = (size - 2) / 2; i >= 0; i--)
//從第一個非葉子節點開始調整,此處i必須給int,給size_t會死迴圈
{
_Adjustdown(i);
}
}
void Pushback(const T& n)//
{
_a.push_back(n);
size_t child = _a.size() - 1;
_Adjustup(child);
}
void Pop()
{
assert(!_a.empty());
swap(_a[0], _a[_a.size() - 1]);
_a.pop_back();
_Adjustdown(0);
}
size_t Size()
{
return _a.size();
}
bool Empty()
{
return _a.empty();
}
const T& Top()
{
return _a[0];
}
protected:
void _Adjustdown(size_t root)//向下調整
{
size_t child = root * 2 + 1;
while (child<_a.size())
{
Compare com;
if ((child + 1)<_a.size() && com(_a[child+1],_a[child]))
/*對括號內左右物件進行比較,若是Less,則當左值小於右值時為真
若是Greater,則當左值大於右值時為真*/
{
++child;
}
if (com(_a[child] , _a[root]))
{
swap(_a[child], _a[root]);
root = child;
child = root * 2 + 1;
}
else
{
break;
}
}
}
void _Adjustup(int child)//向上調整
{
int parent = (child - 1) / 2;
while (parent >= 0)
{
Compare com;
if (com(_a[child] , _a[parent]))
{
swap(_a[parent], _a[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
protected:
vector<T> _a;
};
void TestHeap()
{
int a[] = { 10, 11, 13, 12, 16, 18, 15, 17, 14, 19 };
Heap<int> hp(a, sizeof(a) / sizeof(a[0]));
hp.Pushback(20);
hp.Pop();
}
堆的應用有很多,下面我們來看一下
一.優先順序佇列
優先順序佇列是不同於先進先出佇列的另一種佇列。每次從佇列中取出的是具有最高優先權的元素,這裡的最高優先權就可以理解為最大堆和最小堆的Top元素,通過上面實現的堆來實現優先順序佇列
template <class T,class Compare=Greater<T>>
class PriorityQueue
{
public:
PriorityQueue()
{}
PriorityQueue(T* a, size_t n)
{
for (size_t i = 0; i < n; ++i)
{
_h.Pushback(a[i]);
}
}
void Push(const T& x)
{
_h.Pushback(x);
}
void Pop()
{
_h.Pop();
}
size_t Size()
{
return _h.Size();
}
bool Empty()
{
return _h.Empty();
}
const T& Top()
{
return _h.Top();
}
protected:
Heap<T, Compare> _h;
};
void TestPriorityQueue()
{
int a[] = { 10, 11, 13, 12, 16, 18, 15, 17, 14, 19 };
PriorityQueue<int> q(a, 10);
//q.Push(20);
//q.Pop();
size_t b = q.Top();
cout << b << endl ;
}
這裡測試的時候我們預設實現大堆,測試結果如下
二.堆排序
我們使用堆排序肯定是因為它的時間複雜度O(N*lgN)相對於其他排序可能更好,
就升序而言,它的基本思想就是建好大堆後,將Top元素和最後一個數據元素交換,此時最後一個元素就是最大的數,再對剩下的數進行向下調整(不包括從Top位置交換下來的元素),把Top元素和倒數第二個元素進行交換,以此類推。降序同樣,建立小堆,思想類似。
升序的程式碼如下
void AdJustDown(int a[], size_t n, int root)
{
if (a == NULL)
{
return;
}
int parent = root;
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n&&a[child] < a[child + 1])
{
child = child + 1;
}
if (a[parent] < a[child])
{
swap(a[parent], a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int a[], size_t n)
{
if (a == NULL)
{
return;
}
for (int i = (n - 2) / 2; i >= 0; --i)
{
AdJustDown(a, n, i);
}
int end = n - 1;
for (size_t i = 0; i < n; ++i)
{
swap(a[0], a[end]);
AdJustDown(a, end, 0);
--end;
}
}
void TestHeapSort()
{
int a[] = { 10, 11, 13, 12, 16, 18, 15, 17, 14, 19 };
HeapSort(a, sizeof(a) / sizeof(a[0]));
for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
{
cout << a[i] << " ";
}
}
測試結果如下
三.TopK問題
給一串陣列,要求找出這串陣列中最大的前K個數。這裡可以使用小堆來實現
template <class T,class Compare>
class TopK
{
public:
TopK()
{}
TopK(int* a,int n,int k)
//n表示陣列大小,k表示前k個
{
assert(k <= n);
size_t i = 0;
while (i < n)
{
//先將陣列的前K個數放入堆中
if (i < k)
{
_minhp.Pushback(a[i]);
}
else
{
if (a[i]>_minhp.Top())
//如果接下去的數比Top元素大,刪除Top元素,插入該元素
{
_minhp.Pop();
_minhp.Pushback(a[i]);
}
}
++i;
}
}
protected:
Heap<T ,Less<T>> _minhp;
};
void TestTopK()
{
int a[] = { 10, 11, 13, 12, 16, 18, 15, 17, 14, 19 };
TopK<int,Less<int>> top(a, sizeof(a) / sizeof(a[0]), 5);
}
測試的堆中結果如下
以上堆中的5個元素即是最大的前5個數。
相關推薦
堆的實現及應用(優先順序佇列,堆排,TopK問題)
堆資料結構是一種陣列物件,它可以被看做是一棵完全二叉樹。 堆的二叉樹儲存有兩種方式: 1.最大堆:每個父節點的值都大於孩子節點 2.最小堆:每個父節點的值都小於小子節點 如上圖所示就是一個最小堆。 關於堆,其實說到底就是兩種演算法,一種是向下調整演
稀疏矩陣的三元組表示的實現及應用(2)——採用三元組儲存稀疏矩陣,設計兩個稀疏矩陣相加的運算演算法
/* *Copyright (c) 2015 , 煙臺大學計算機學院 *All right resvered . *檔名稱: 稀疏矩陣.cpp *作 者: 鄭兆涵 *稀疏矩陣的三元組表示的實現及應用(2) */ 問題:稀疏矩
PHP訊息佇列實現及應用:訊息佇列概念介紹
在網際網路專案開發者經常會遇到『給使用者群發簡訊』、『訂單系統有大量的日誌需要記錄』或者在秒殺業務的時候伺服器無法承受瞬間併發的壓力。 這種情況下,我們怎麼保證系統正常有效的執行呢? 這個時候,我們可以引入一個叫『訊息佇列』的概念來解決上面的需求。 訊息佇列的概
ActiveMQ訊息佇列的使用及應用(二丶JMS基本概念和模型)
一丶JMS基本概念(1) 概念 JMS Java Message Service,Java訊息服務,是Java EE中的一個技術 JMS規範 JMS 定義了Java中訪問訊息中介軟體的介面,並沒有給予實現,實現JMS介面的訊息中介軟體叫JMS Provider,例如ActiveMQ JMS Prov
濾波反投影重建演算法(FBP)實現及應用(matlab)
濾波反投影重建演算法實現及應用(matlab) 1. 濾波反投影重建演算法原理 濾波反投影重建演算法常用在CT成像重建中,背後的數學原理是傅立葉變換:對投影的一維傅立葉變換等效於對原影象進行二維的傅立葉變換。(傅立葉中心切片定理) CT重建演算法大致分為解析重建
第九周專案三:稀疏矩陣的三元組表示的實現及應用(2)
/* Copyright (c)2015,煙臺大學計算機與控制工程學院 All rights reserved. 檔名稱:專案3-2.cbp 作 者:孫立立 完成日期:2015年12月4日 版 本 號:v1.0 問題描述:(2)採用三元組儲存稀疏矩陣,設計兩個稀疏
稀疏矩陣的三元組表示的實現及應用(1)——建立稀疏矩陣三元組表示的演算法庫
/* *Copyright (c) 2015 , 煙臺大學計算機學院 *All right resvered . *檔名稱: 稀疏矩陣.cpp *作 者: 鄭兆涵 *稀疏矩陣的三元組表示的實現及應用(1) */ 問題:稀疏
演算法導論 第六章:堆排序 筆記(堆、維護堆的性質、建堆、堆排序演算法、優先順序佇列、堆排序的程式碼實現)
堆排序(heapsort) 像合併排序而不像插入順序,堆排序的執行時間為O(nlgn) 。像插入排序而不像合併排序,它是一種原地( in place) 排序演算法:在任何時候,陣列中只有常數個元素儲存在輸入陣列以外。 堆: (二叉)堆資料結構是一種陣列物件,它可以被視為一棵完全二叉樹。樹
系統學習深度學習(五) --遞迴神經網路原理,實現及應用
但是大神們說,標準的RNN在實際使用中效果不是很好,真正起到作用的是LSTM,因此RNN只做簡單學習,不上原始碼(轉載了兩篇,第一個是簡單推導,第二個是應用介紹)。 下面是簡單推導,轉自:http://blog.csdn.net/aws3217150/article/details/5076
Qt容器類的對象模型及應用(線性結構篇:對於QList來說,sharable默認是false的,但對於接下來講的QVector來說,sharable默認是true)
連續 ble begin 當我 保存 img article net 內容 用Qt做過項目開發的人,肯定使用過諸如QList、QVector、QLinkList這樣的模板容器類,它們雖然名字長的不同,但使用方法都大致相同, 因為其使用方法都大體相同,很多人可能隨便拿一個容器
ml課程:最大熵與EM演算法及應用(含程式碼實現)
以下是我的學習筆記,以及總結,如有錯誤之處請不吝賜教。 本文主要介紹最大熵模型與EM演算法相關內容及相關程式碼案例。 關於熵之前的文章中已經學習過,具體可以檢視:ml課程:決策樹、隨機森林、GBDT、XGBoost相關(含程式碼實現),補充一些 基本概念: 資訊量:資訊的度量,即
Linux下批量部署(Pxe、Kickstart實現及應用)
導讀: 作為運維經常會遇到一些重複的工作,例如:有時公司同時上線幾十甚至上百臺伺服器,而且需要我們在 短時間內完成系統安裝。本文主要講述了Linux下批量部署 Pxe、Kickstart實現及應用. 一、相關知識提要 1.理論知識 PXE : PXE(pre
Redis 原理及應用(3)--記憶體淘汰機制、主從同步原理,HA策略(哨兵機制)分析
非精準的LRU 上面提到的LRU(Least Recently Used)策略,實際上Redis實現的LRU並不是可靠的LRU,也就是名義上我們使用LRU演算法淘汰鍵,但是實際上被淘汰的鍵並不一定是真正的最久沒用的,這裡涉及到一個權衡的問題,如果需要在全部鍵空間內搜尋最優解,則必然會增加系統的開銷,Re
指標陣列,陣列指標,函式指標及應用(回撥函式)
················································索引··························································· 當我們在學習指標與陣
雲從科技資深演算法研究員詳解跨境追蹤(ReID)技術實現及應用場景
跨鏡追蹤(Person Re-Identification,簡稱 ReID)技術是當前計算機視覺研究的熱門方向,主要解決跨攝像頭跨場景下行人的識別與檢索。跨鏡追蹤(ReID)技術能夠根據行人的穿著、體態、髮型等資訊認知行人,與人臉識別技術結合能夠適用於更多新的應用場景,提供更
Java代理模式及應用(三)Cglib實現
前一節所說的靜態代理和動態代理模式都是要求目標物件是實現一個介面的目標物件,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理 1.Cglib說明 Cglib代理,
PHP訊息佇列實現及應用_慕課網學習
https://blog.csdn.net/d_g_h/article/details/79643714 https://blog.csdn.net/tTU1EvLDeLFq5btqiK/article/details/80971792 目前對訊息佇列並不瞭解其原理,本篇文章主要是通過慕課
PHP訊息佇列實現及應用:佇列處理訂單系統和配送系統
解耦案列:佇列處理 訂單系統和配送系統 這裡,我們要來處理其中一個場景:系統的解耦。 在電商專案中,當客戶提交了一個訂單之後,客戶在個人中心可以看到訂單處於配送中。 這個時候就要參與進來一個系統,叫做『配送系統』。如果我們在做架構的時候,把訂單系統和配
ELK部署logstash安裝部署及應用(二)
日誌 elk elkstack Logstash 安裝部署註意事項: Logstash基本概念:logstash收集日誌基本流程: input-->codec-->filter-->codec-->outputinput:從哪裏收集日誌。filter:發出去前進行過濾out
隊列的實現及操作(C語言描述)
img tdi 定義數據 上一個 判斷 free 隊列的單鏈表 插入數據 尾指針 // 隊列的單鏈表實現 // 頭節點:哨兵作用,不存放數據,用來初始化隊列時使隊頭隊尾指向的地方 // 首節點:頭節點後第一個節點,存放數據 #include&