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

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. QT学习之菜单栏与工具栏

    QT学习之菜单栏与工具栏 目录 简单菜单栏 多级菜单栏 上下菜单栏 工具栏 简单菜单栏 程序示例 from PyQt5.QtWidgets import QApplication, QMainWind ...

  2. Linux系统之常用文件搜索命令

    (一)常用文件搜索命令 (1)which命令 (2)find命令 (3)locate (4)updatedb (5)grep (6)man (7)whatis (一)常用文件搜索命令 (1)which ...

  3. IdentityServer4-用EF配置Client(一)

    一.背景 IdentityServer4的介绍将不再叙述,百度下可以找到,且官网的快速入门例子也有翻译的版本.这里主要从Client应用场景方面介绍对IdentityServer4的应用. 首先简要介 ...

  4. C++函数模版的简单使用

    模版算是C++的独有特性吧,也算是C++中比较难的地方,我平时开发的时候用的非常少,或者几乎没有用到,需要模版的地方是能看懂框架中相关的代码: 模版函数相对还是很简单的,引入模版的目的在于代码的重用: ...

  5. windows命令行下杀死进程的方法

    xp和win7下有两个好东东tasklist和tskill.tasklist能列出所有的进程,和相应的信息.tskill能查杀进程,语法很简单:tskill程序名!或者是tskill 进程id 例如: ...

  6. android之官方导航栏ActionBar(三)之高仿优酷首页

    一.问题概述 通过上两篇文章,我们对如何使用ActionBar大致都已经有了认识.在实际应用中,我们更多的是定制ActionBar,那么就需要我们重写或者定义一些样式来修饰ActionBar,来满足具 ...

  7. wifipineapple获取用户上网信息

    ssh连接到wifipineapple: 输入连接信息:ssh root@172.16.42.1 输入密码:pineapplesareyummy 安装依赖基本环境: opkg update opkg ...

  8. IDependency自动注册autofac

    ContainerBuilder builder = new ContainerBuilder(); builder.RegisterGeneric(typeof(Repository<,> ...

  9. InfluxDB源码目录结构解析

    操作系统 : CentOS7.3.1611_x64 go语言版本:1.8.3 linux/amd64 InfluxDB版本:1.1.0 influxdata主目录结构 [root@localhost ...

  10. Duplicate复制数据库并创建物理StandBy(spfile+不同实例名)

    过程和Duplicate复制数据库并创建物理StandBy类似,只是不需要重启数据库. 目的:创建standby,不重启源数据库 1设定环境如下: Primary数据库 IP 172.17.22.16 ...