数据结构之(二叉)堆一文在末尾提到“利用堆能够实现:堆排序、优先队列。”。本文代码实现之。

1、堆排序

如果要实现非递减排序。则须要用要大顶堆。

此处设计到三个大顶堆的操作:(1)自顶向下调整操作:MaxHeapify(相应堆的SiftDown操作)、(2)利用数组建立大顶堆:BuildMaxHeap、(3)不断交换堆顶元素(堆的最大元素)和堆的末尾元素,实现非递减排序。

以下是详细的实现代码:

//已知L[i,...,n)除L[i]之外均满足大顶堆的定义,本函数向下调整L[i]
//使得在具有n个结点的堆中,以i为下标的根节点的子树又一次遵循最大堆的性质
//n为节点总数,从0開始计算。i节点的子节点为 2*i+1, 2*i+2
void MaxHeapify(int L[], int i, int n)
{
int j, tmp;
j = 2 * i + 1; //父节点i的左孩子结点
tmp = L[i];
while (j < n)
{
if (j + 1 < n && L[j + 1] > L[j])//找左右孩子中最大的
j++;
if (L[j] <= tmp) // 父结点tmp=L[i]比孩子结点L[j]大
break;
L[i] = L[j]; //把较大的子结点往上移动,替换它的父结点
i = j;
j = 2 * i + 1;
}
L[i] = tmp;
}
//子数组L[n/2+1,..,n)中的元素都是树的叶子结点,每一个叶节点都能够看成仅仅包括一个元素的堆。
//该函数对树中的其余结点都调用一次MaxHeapify()
void BuildMaxHeap(int L[], int n)
{
for (int i = n / 2 - 1; i >= 0; i--) //注意是从最后一个结点(n-1)的父节点((n-1)-1)/2 = (n-2)/2 = n/2-1 開始
MaxHeapify(L, i, n);
}
void MaxHeapSort(int L[], int n)
{
BuildMaxHeap(L, n); //先建立大顶堆
for (int i = n - 1 ; i > 0; i--) //从最后一个元素起
{
//L[0]永远是大顶堆堆L[0,i]的堆顶元素,最大值
std::swap(L[0], L[i]); //将L[0]和最末尾元素交换后:最大的元素如今位于数组末尾,堆的结点个数减一
//但L[0..i)由于改动了L[0]可能会破坏堆的性质,因此须要调整L[0,...,i)使之依旧满足最大堆:
//已知L[0,...,i)除L[0]之外均满足大顶堆的定义,本函数向下调整L[0]
//使得在具有i个结点的堆中,以0为下标的根节点的子树又一次遵循最大堆的性质
MaxHeapify(L, 0, i);
}
}

2、利用堆实现优先队列

优先队列分为最大优先队列和最小优先队列,分别借助于大顶堆和小顶堆。

优先队列有以下基本操作:(1)提取队列中的最大(小)元素;(2)提取队列中的最大(小)元素并从队列中删除;(3)将队列中元素为x的keyword降低(增大)到k,这里如果k的值不大(小)于x的原关键值

其它的还包含如插入、删除操作。这些操作大多调用SiftDown、SiftUp操作实现。以下是详细实现代码:

(1)最大优先队列

#pragma once
#include "maxHeap.h" template<class T>
class MaxPriQueue : public MaxHeap<T>
{
public:
MaxPriQueue(const int nmax = 20) : MaxHeap(nmax) {}
~MaxPriQueue(){} T maximum() const; //返回具有最大键值的元素
T ExtractMax(); //去掉并返回最大键值的元素
void InCreaseKey(const T &x, const T &k); //将元素x的keyword添加到k,这里如果k的值不小于x的原关键值
virtual void Insert(const T &key);//算法导论第三版p92的方法;也能够调用继承来的Insert方法
}; template<class T>
T MaxPriQueue<T>::maximum()const
{
if (size > 0)
return arr[0];
return T(0);
} template<class T>
T MaxPriQueue<T>::ExtractMax()
{
if (size < 0)
return T(0);
T max = arr[0];
arr[0] = arr[--size];//最末尾的值补上去,同一时候size减1
SiftDown(0); //向下调整
return max;
} template<class T>
void MaxPriQueue<T>::InCreaseKey(const T &x, const T &k)
{
if (k < x) //如果k的值不小于x的原关键值
return;
int pos = Search(x);
if (pos == -1)
return;
arr[pos] = k;
SiftUp(pos); //向上调整
}
template<class T>
void MaxPriQueue<T>::Insert(const T &key)
{
arr[size++] = -INFINITY; //首先添加一个-∞的叶节点
InCreaseKey(-INFINITY, key); //将该叶节点的值增大至key
}

(2)最小优先队列

#pragma once
#include "minHeap.h" template<class T>
class MinPriQueue : public MinHeap<T>
{
public:
MinPriQueue(const int nmax = 20) : MinHeap(nmax) {}
~MinPriQueue(){} T minimum() const; //返回具有最小键值的元素
T ExtractMin(); //去掉并返回最小键值的元素
void DeCreaseKey(const T &x, const T &k); //将元素x的keyword降低到k,这里如果k的值不大于x的原关键值
virtual void Insert(const T &key);//算法导论第三版p92的方法;也能够调用继承来的Insert方法
}; template<class T>
T MinPriQueue<T>::minimum()const
{
if (size > 0)
return arr[0];
return T(0);
} template<class T>
T MinPriQueue<T>::ExtractMin()
{
if (size < 0)
return T(0);
T min = arr[0];
arr[0] = arr[--size];//最末尾的值补上去,同一时候size减1
SiftDown(0); //向下调整
return min;
} template<class T>
void MinPriQueue<T>::DeCreaseKey(const T &x, const T &k)
{
if (k >= x) //如果k的值不大于x的原关键值
return;
int pos = Search(x);
if (pos == -1)
return;
arr[pos] = k;
SiftUp(pos); //向上调整
}
template<class T>
void MinPriQueue<T>::Insert(const T &key)
{
arr[size++] = INFINITY; //首先添加一个∞的叶节点
DeCreaseKey(INFINITY, key); //将该叶节点的值降低至key
}

測试代码:

#include "max_priqueue.h"
#include "min_priqueue.h"
#include <iostream>
using namespace std; int main()
{
int a[12] = {15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1};
int b[12]; memcpy(b, a, sizeof(a)); cout << "原始数组元素: ";
for (int i = 0; i < 12; i++)
cout << a[i] << " ";
cout << endl; MaxPriQueue<int> maxpriqueue(20);
MinPriQueue<int> minpriqueue(20);
for (int i = 0; i < 12; i++)
maxpriqueue.Insert(a[i]); //向上调整 cout << "\n最大优先队列元素为: "; maxpriqueue.Print();
cout << "max = " << maxpriqueue.maximum() << endl;
cout << "删掉最大元素 " << maxpriqueue.ExtractMax() << "后 :" ; maxpriqueue.Print();
cout << "将元素 12 添加到 23 后:"; maxpriqueue.InCreaseKey(12, 23);maxpriqueue.Print();
cout << "删掉元素4后: "; maxpriqueue.Delete(4); maxpriqueue.Print(); for (int i = 0; i < 12; i++)
minpriqueue.Insert(a[i]); //向上调整
cout << "\n最小优先队列元素为: "; minpriqueue.Print();
cout << "min = " << minpriqueue.minimum() << endl;
cout << "删掉最小元素 " << minpriqueue.ExtractMin() << "后 :" ; minpriqueue.Print();
cout << "将元素 12 减小到 3 后:"; minpriqueue.DeCreaseKey(12, 23);minpriqueue.Print();
cout << "删掉元素4后: "; minpriqueue.Delete(4); maxpriqueue.Print(); getchar();
return 0;
}

执行截图:

ps:代码中的继承的类的代码能够在 数据结构之(二叉)堆 中查看;

向量排序算法:不断将无序数组中的元素插入最小优先队列中,构建完成后。再调用n次ExtractMin()操作。也能够实现非递减排序,可是要花费O(n)的空间复杂度。(编程珠玑:p148)

3、STL中的堆(heap)

STL中与堆相关的4个函数——建立堆make_heap()、在堆中加入数据push_heap()、在堆中删除数据pop_heap()和堆排序sort_heap()。



头文件 #include <algorithm>



以下的_First与_Last为能够随机訪问的迭代器(指针),_Comp为比較函数(仿函数)。其规则——假设函数的第一个參数小于第二个參数应返回true,否则返回false。



(1)建立堆



make_heap(_First, _Last, _Comp)



默认是建立大顶堆的(less<T>())。对int类型。能够在第三个參数传入greater<int>()得到最小堆。最小堆的时候的push_heap()、pop_heap()、sort_heap()均要显示地将greater<int>()作为第三个參数传入。



(2)在堆中加入数据



push_heap (_First, _Last)



要先在容器中加入数据:push_back(x)操作;再调用push_heap ():将最末尾新加入的元素向上调整使之整个容器数组符合堆性质。

(3)在堆中删除数据



pop_heap(_First, _Last)



要先调用pop_heap()操作:将堆顶元素和堆的最末尾元素交换,堆大小减去1。并向下调整堆顶元素使得新的堆满足最大堆的性质;再在容器中删除数据:pop_back()操作。每次删除的是堆顶的元素



(4)堆排序



sort_heap(_First, _Last)



排序之后就不再是一个合法的heap了。

STL中heap使用演示样例代码:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional> //for function object like greater<int>()
#include <ctime>
using namespace std; void print(vector<int> &vet)
{
for (vector<int>::const_iterator iter = vet.begin(); iter != vet.end(); iter++)
cout << *iter << " ";
cout << endl;
}
int main()
{
const int MAXN = 10;
int a[MAXN];
srand((unsigned)time(0));
for (int i = 0; i < MAXN; ++i)
a[i] = rand() % (MAXN * 2); //动态申请vector,并对vector建堆
vector<int> *pvet = new vector<int>(20);
pvet->assign(a, a + MAXN); cout << "\n无序数组:\t\t";print(*pvet); //默认建大顶堆
make_heap(pvet->begin(), pvet->end());
cout << "大顶堆:\t\t\t";print(*pvet); //加入数据
pvet->push_back(25); //先在容器中加入
cout << "\n容器push_back(25)后:\t";print(*pvet);
push_heap(pvet->begin(), pvet->end()); //再调用push_heap()
cout << "堆push_heap()操作后:\t";print(*pvet); //删除数据
pop_heap(pvet->begin(), pvet->end()); // 先调用pop_heap()
cout << "\n堆pop_heap()操作后:\t";print(*pvet);
pvet->pop_back(); //再在容器中删除
cout << "容器pop_back()后:\t";print(*pvet);
pop_heap(pvet->begin(), pvet->end());
cout << "\n堆pop_heap()操作后:\t";print(*pvet);
pvet->pop_back();
cout << "容器pop_back()后:\t";print(*pvet); //堆排序
sort_heap(pvet->begin(), pvet->end());
cout << "\n堆排序结果:\t\t";print(*pvet); delete pvet;
return 0;
}

演示样例截图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMzA3MTA3NA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

4、利用堆求解top_k

编写算法。从10亿个浮点数其中,选出其中最大的10000个。

//典型的Top K问题,用堆是最典型的思路。

//建10000个数的小顶堆,然后将10亿个数依次读取,大于堆顶,则替换堆顶。做一次堆调整。
//结束之后,小顶堆中存放的数即为所求。 代码例如以下(为了方便,这里直接使用了STL容器): void top_k()
{
const int nmax = 1000000000; //10亿个数
vector<float>bigs(10000, 0); //init vector array
srand((unsigned)time(0));
for (vector<float>::iterator iter = bigs.begin(); iter != bigs.end(); iter++)
*iter = (float)rand() / 7; //random values;
//cout << bigs.size() << endl; make_heap(bigs.begin(), bigs.end(), greater<float>());//little heap, the first one is the smallest one! float f;
for (int i = 0; i < nmax; i++)
{
srand((unsigned)time(0));
f = (float)rand() / 7;
if (f > bigs.front())//replace the first element? {
//set the smallest one to the end!
pop_heap(bigs.begin(), bigs.end(), greater<float>());
//remove the smallest one
bigs.pop_back();
//add to the last one
bigs.push_back(f);
//make the heap again, the first element is still the smallest one
push_heap(bigs.begin(), bigs.end(), greater<float>());
}
}
//sort by ascent
sort_heap(bigs.begin(), bigs.end(), greater<float>());
}

參考资料:数据结构p297-p283、算法导论第三版:p90-p92、编程珠玑:p145-p148

STL系列之四 heap 堆

利用堆实现堆排序&amp;优先队列的更多相关文章

  1. 【ZZ】堆和堆的应用:堆排序和优先队列

    堆和堆的应用:堆排序和优先队列 https://mp.weixin.qq.com/s/dM8IHEN95IvzQaUKH5zVXw 堆和堆的应用:堆排序和优先队列 2018-02-27 算法与数据结构 ...

  2. 堆的源码与应用:堆排序、优先队列、TopK问题

    1.堆 堆(Heap))是一种重要的数据结构,是实现优先队列(Priority Queues)首选的数据结构.由于堆有很多种变体,包括二项式堆.斐波那契堆等,但是这里只考虑最常见的就是二叉堆(以下简称 ...

  3. 堆排序与优先队列——算法导论(7)

    1. 预备知识 (1) 基本概念     如图,(二叉)堆是一个数组,它可以被看成一个近似的完全二叉树.树中的每一个结点对应数组中的一个元素.除了最底层外,该树是完全充满的,而且从左向右填充.堆的数组 ...

  4. python下实现二叉堆以及堆排序

    python下实现二叉堆以及堆排序 堆是一种特殊的树形结构, 堆中的数据存储满足一定的堆序.堆排序是一种选择排序, 其算法复杂度, 时间复杂度相对于其他的排序算法都有很大的优势. 堆分为大头堆和小头堆 ...

  5. JAVA数据结构(十一)—— 堆及堆排序

    堆 堆基本介绍 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,最坏,最好,平均时间复杂度都是O(nlogn),不稳定的排序 堆是具有以下性质的完全二叉树:每个节点的值都大于或等 ...

  6. 堆与堆排序/Heap&Heap sort

    最近在自学算法导论,看到堆排序这一章,来做一下笔记.堆排序是一种时间复杂度为O(lgn)的原址排序算法.它使用了一种叫做堆的数据结构.堆排序具有空间原址性,即指任何时候都需要常数个额外的元素空间存储临 ...

  7. 洛谷P3371单源最短路径Dijkstra堆优化版及优先队列杂谈

    其实堆优化版极其的简单,只要知道之前的Dijkstra怎么做,那么堆优化版就完全没有问题了. 在做之前,我们要先学会优先队列,来完成堆的任务,下面盘点了几种堆的表示方式. priority_queue ...

  8. PHP面试:说下什么是堆和堆排序?

    堆是什么? 堆是基于树抽象数据类型的一种特殊的数据结构,用于许多算法和数据结构中.一个常见的例子就是优先队列,还有排序算法之一的堆排序.这篇文章我们将讨论堆的属性.不同类型的堆以及堆的常见操作.另外我 ...

  9. Java实现的二叉堆以及堆排序详解

    一.前言 二叉堆是一个特殊的堆,其本质是一棵完全二叉树,可用数组来存储数据,如果根节点在数组的下标位置为1,那么当前节点n的左子节点为2n,有子节点在数组中的下标位置为2n+1.二叉堆类型分为最大堆( ...

随机推荐

  1. web漏洞扫描工具AWVS使用

    AWVS AWVS简介:Acunetix Web Vulnerability Scanner(简称AWVS)是一款知名的网络漏洞扫描工具,它通过网络爬虫测试你的网站安全,检测流行安全漏洞,如交叉站点脚 ...

  2. Javascript中call,apply,bind的区别

    一.探索call方法原理 Function.prototype.call = function(obj) { // 1.让fn中的this指向obj // eval(this.toString().r ...

  3. Cocos2d-X开发教程-捕鱼达人 Cocos2-x development tutorial - fishing talent

    Cocos2d-X开发教程-捕鱼达人 Cocos2-x development tutorial - fishing talent 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱 ...

  4. BZOJ.2756.[SCOI2012]奇怪的游戏(二分 黑白染色 最大流ISAP)

    题目链接 \(Description\) \(Solution\) 这种题当然要黑白染色.. 两种颜色的格子数可能相同,也可能差1.记\(n1/n2\)为黑/白格子数,\(s1/s2\)为黑/白格子权 ...

  5. Oracle 11g透明网关连接Sqlserver 2000(转)

    Oracle 11g透明网关连接Sqlserver 2000: http://www.cnblogs.com/lightnear/archive/2013/02/03/2890858.html 透明网 ...

  6. .net core 3.0视图动态编译

    之前在使用Visual Studio 2019的时候,就发现asp.net 3.0中没有cshtml动态编译的功能了:也就是说,如果改了cshtml,刷新页面不会立即生效,而是要重新编译一次才行. 这 ...

  7. 使用OClint进行iOS项目的静态代码扫描

    使用OClint进行iOS项目的静态代码扫描 原文链接:http://blog.yourtion.com/static-code-analysis-ios-using-oclint.html 最近需要 ...

  8. C#模拟HTTP请求Post JSON

    前言 因为接口是http的,我们站点是https的,不能https直接ajax请求http,所以需要在SharePoint中开发一个模拟请求Ajax的Service,分享一下. var httpWeb ...

  9. Windows如何安装pip

    下载这个文件:  https://bootstrap.pypa.io/get-pip.py 然后到下载目录执行Python命令:   (管理员权限执行) python get-pip.py

  10. Redis集群简记

    Redis集群 http://doc.redisfans.com/topic/cluster-tutorial.html redis 集群是为了多个节点之间数据的共享和集群高可用的保证. redis ...