堆的概念以及实现堆
来源:互联网 发布:淘宝大马士革刀真假 编辑:程序博客网 时间:2024/06/05 03:19
普通队列就是遵循时间原则,先进先出,后进后出的一种队列结构;而优先队列则是按照优先级来决定出队的次序,与何时进入队列无关,实际生活中有很多优先队列的例子:医院候诊病人中,急诊病人优先看病,而不是按照病人到达医院的先后次序来,这就是一个优先队列的例子。
优先队列适合应用于动态场景中,比如游戏中自动作战,当存在多个敌人时,不是按照敌人出现的顺序进行攻击,而是根据某个优先级(比如先攻击血量最大的)进行攻击,为什么说是动态的?是因为不是在攻击完血量最大的敌人之后就接着攻击剩余的敌人,实际上还会有其他的敌人陆续出现,这时候再在所有敌人中选出优先级最高的进行攻击。
实际上,除了在动态数据的处理上优先队列很有优势,优先队列在处理静态数据时有时也是很有优势的。
优先队列的实现方式有几种:
当总共需要进行N次时,采用普通数组和顺序数组实现的优先队列的时间复杂度最差为O(n^2),而采用堆实现的优先队列时间复杂度为O(Nlogn)。
接下来介绍堆这种数据结构。
什么是堆?经典的是二叉堆,对应的就是一棵二叉树,二叉堆又分为最大堆和最小堆。最大堆:堆中每个节点的值都不大于其双亲节点的值,根结点为最大值;最小堆:堆中每个节点的值都不小于其双亲节点的值,根结点为最小值。二叉堆总是一棵完全二叉树。
二叉堆的经典存储方式是使用数组来存储。
根据这种存储方式,对数组中下标为 i 的元素,其双亲结点就对应数组中下标为 i/2 的元素,其左孩子为数组中下标为 2i 的元素,其右孩子为数组中下标为 2i+1 的元素。
接下来以最大堆为例,用C++实现最大堆:
#include <iostream>#include <cassert>#include <algorithm>#include <cstdlib>#include <ctime>using namespace std;template<typename Item>class MaxHeap{private: Item *data; int count; int capacity; void shiftUp(int k) { //凡是涉及到数组下标的,都要限定它不出界 while(k > 1 && data[k] > data[k / 2]) { swap(data[k], data[k / 2]); k /= 2; } } void shiftDown(int k) { //当k有孩子的情况下开始shiftDown操作 while( 2*k <= count ) { int j = 2*k; //j就是要和data[k]交换的元素的位置下标 //判断是否有右孩子 if( j+1 <= count && data[j+1] > data[j] ) { j = j + 1; } if(data[k] >= data[j]) { break; } swap(data[k], data[j]); k = j; } }public: MaxHeap(int capacity) { data = new Item[capacity + 1]; //从1号单元开始存放 count = 0; this->capacity = capacity; } ~MaxHeap() { delete[] data; } //返回堆现在的大小 int size() const { return count; } //判断堆是否为空堆 bool isEmpty() const { return count == 0; } //向堆中插入一个元素 void insert(Item item) { //判断count+1是否出界 assert(count + 1 <= capacity); data[count + 1] = item; count++; //从count位置处开始向上调整堆 shiftUp(count); //这个函数不需要被用户调用,所以设为私有 } //取出堆中的一个元素(只能取出根结点) Item extractMax() { //首先判断堆是否为空 assert(count > 0); Item ret = data[1]; data[1] = data[count]; count--; shiftDown(1); return ret; } //打印堆中内容 void printData() const { for(int i = 1; i <= count; i++) { cout << data[i] << " "; } cout << endl; }};int main(){ MaxHeap<int> maxheap = MaxHeap<int>(100); //利用构造函数初始化 cout << maxheap.size() << endl; //随机插入几个元素 srand(time(0)); while(maxheap.size() < 10) { int item = rand() % 100 + 1; maxheap.insert(item); } cout << maxheap.size() << endl; maxheap.printData(); //取出堆中元素(实际上就是将堆中元素从大到小排序的过程) while(!maxheap.isEmpty()) { cout << maxheap.extractMax() << " "; } cout << endl; return 0;}我们将最大堆声明为一个类,因为堆的大小是由用户定义的,所以使用指针 Item *data 来动态分配内存(使用构造函数 MaxHeap(int capacity)来实现),在构造函数中使用 new[] 动态分配内存后,相应的就需要在析构函数中 delete[] 内存。
接下来分析向堆中插入元素的过程:只能在数组的最后一个位置插入元素,此时的完全二叉树不满足堆的定义,所以需要进行向上调整堆的过程,若新插入节点值大于其双亲节点的值,则交换两者位置,重复此步骤直至根结点(由于用户只需要调用插入元素的函数,不需要调用向上调整的函数,所以将shiftUp(int k)定义在私有成员中)。
接下来分析取出堆中元素的过程:只能取出根节点的值(最大值),此时根节点的值用堆中最后一个元素的值代替,此时新的完全二叉树不满足堆的定义,所以需要向下调整堆。若根节点的值小于其左右孩子中的较大值,则交换两者位置,重复此步骤,直到其没有孩子为止(由于用户只需要调用取出元素的函数,不需要调用向下调整的函数,所以将shiftDown(int k)定义在私有成员中)。
在main函数中,最后的while(!maxheap.isEmpty())循环按从大到小的顺序打印出堆中所有元素,实际上就是一个排序的过程,打印结果如下:
在上述代码中,需要多次进行swap操作,这是一项耗时的操作,最好使用赋值操作来取代之:即先找到要交换的位置,然后赋值。优化后的 shiftUp() 和 shiftDown() 代码如下:
void shiftUp(int k) { //凡是涉及到数组下标的,都要限定它不出界 //优化:用赋值取代交换 Item item = data[k]; while( k>1 && data[k/2] < item ) { data[k] = data[k/2]; k /= 2; } data[k] = item; } void shiftDown(int k) { //当k有孩子的情况下开始shiftDown操作 //优化:用赋值取代交换 Item item = data[k]; while( 2*k <= count ) { int j = 2*k; if(j + 1 <= count && data[j + 1] > data[j]) { j = j + 1; } if(data[j] <= item) { break; } data[k] = data[j]; k = j; } data[k] = item; }
到这里,如何实现最大堆就讲完了,下一节将实现堆排序以及其优化。
- 堆的概念以及实现堆
- 堆数据结构的实现以及堆排序
- 【堆】二分堆的实现以及STL中的堆
- 堆和栈的概念以及区别
- 堆以及堆排序实现
- 堆的概念及基本操作实现
- 堆的相关概念与实现
- 栈、堆的概念
- 堆的创建、插入、删除,模板参数实现堆以及模板的模板参数实现堆
- 最大最小堆的操作以及实现
- 最小堆的介绍以及实现
- 堆的实现以及优先级队列
- c++实现堆类以及堆排序
- Java实现堆以及堆排序
- 堆以及php实现堆排序
- 操作系统意义上的堆和栈的概念以及数据结构意义上的堆和栈的概念
- 操作系统意义上的堆和栈的概念以及数据结构意义上的堆和栈的概念
- 堆的构建以及利用堆排序
- c# 命令行操作数据库并将数据导出到csv
- SpringMVC的注解
- C#结构体+结构体与类的区别
- IOS-TableView里面cell的显示动画
- SPOJ 694 后缀数组
- 堆的概念以及实现堆
- 自己实现一个Native方法的调用
- 插入排序
- 【LAYABOX】轴心点的解释说明
- Spark源码分析之job提交后转换为Stage
- 那些年你用过的工具--网络工具Wireshark经验谈
- MFC中,在图片上添加汉字(c++实现)
- 关于停车计费系统的几个问题
- Unity3D 设计模式---工厂模式