STL之heap相关操作算法

说明:本文仅供学习交流,转载请标明出处,欢迎转载!

堆(heap)是一种非常重要的数据结构(这里我们讨论的是二叉堆),它是一棵满足特定条件的完全二叉树,堆的定义如下:

堆是一棵树完全二叉树,对于该完全二叉树中的每一个结点x,其关键字大于等于(或小于等于)其左右孩子结点,而其左右子树均为一个二叉堆。

在上述的定义中,若堆中父亲结点关键字的值大于等于孩子结点,则称该堆为大顶堆;若堆中父亲结点关键子的值小于等于孩子结点,则称该堆为小顶堆

由于堆是一棵完全二叉树,所以我们可以很轻易地用一个数组存储堆中的每一个元素,并且由子结点访问到其父亲结点和由父亲结点访问到其子结点。下面给出图来说明该表示方法:

下面我们给出学数据结构时堆数组进行堆排序的整个过程:建堆、出堆、向上调整、向下调整等过程。

  1. ////////////////堆排序(大顶堆)//////////////////////////
  2. ///////堆是一个完全二叉树,若结点索引从0开始,则i结点的左孩子为2*i+1,右为2*i+2
  3. void HeapAdjustDown(int *arr,int length,int i)//对第i个值做向下调整,i=0,...,length-1
  4. {
  5. if(arr==NULL || length<=0 || i<0)
  6. {
  7. return ;
  8. }
  9. int temp=arr[i];
  10. int j;//i相当于前驱,j相当于后继
  11. for(j=2*i+1;j<length;)//即j<=length-1
  12. {
  13. if(j+1<length && arr[j]<arr[j+1])//如果存在右孩子,且右孩子值待遇
  14. {
  15. j++;//与右孩子交换
  16. }
  17. if(arr[j]<=temp)//如果左右孩子中的较大值小于该结点值,则说明无需向下调整了
  18. {
  19. break;
  20. }
  21. else//如果左右孩子中的较大值大于该结点值,则想到直接插入排序
  22. {
  23. arr[i]=arr[j];//孩子结点值中较大的上移动
  24. i=j;//i用于追踪待插入点的位置
  25. j=2*i+1;
  26. }
  27. }
  28. if(arr[i]!=temp)//如果i的值发生了移动
  29. {
  30. arr[i]=temp;//将最初第i个节点值放入合适的位置
  31. }
  32. }
  33. void CreateHeap(int *arr,int length)//创建一个堆
  34. {
  35. if(arr==NULL || length<=0)
  36. {
  37. return ;
  38. }
  39. int i;
  40. for(i=(length-2)/2;i>=0;i--)//最后一个元素的序号为length-1,故该结点的父结点为(length-2)/2
  41. {
  42. HeapAdjustDown(arr,length,i);//从倒数第二行开始倒着向下调整
  43. }
  44. }
  45. void HeapDelete(int *arr,int len)//删堆顶元素,len表示当前表的长度
  46. {
  47. if(arr==NULL || len<=0 || len==1)//len=1,表示当堆中只有一个元素时,无需排序
  48. {
  49. return ;
  50. }
  51. else
  52. {
  53. swap(arr[0],arr[len-1]);
  54. HeapAdjustDown(arr,len-1,0);//删除后,数组长度减1了
  55. }
  56. }
  57. void HeapSort(int *arr,int length)//堆排序
  58. {
  59. if(arr==NULL || length<=1)
  60. {
  61. return ;
  62. }
  63. CreateHeap(arr,length);//先创建一个堆0
  64. int i;
  65. for(i=0;i<=length-1;i++)//删除length-1次即可,因为最后一个元素就剩自己了
  66. {
  67. HeapDelete(arr,length-i);
  68. }
  69. }

STL的魅力之一在于能够对特定类型的数据结构提供泛型化,并且提供高效的函数接口。没错,STL不仅实现了上述算法,而且还弥补上述算法的不足。

从上面的代码中,我们发现的不足之处在于:整个过程都是针对一个静态数组,静态数组在插入和删除方面表现的特别笨。所以,STL以动态数组vector为底层实现,提供了几个重要的关于堆的几个重要操作,这些操作分别是:建堆操作、插入操作、删除操作、堆排序操作,下面分别给出这几种操作的函数原型和相关说明。

建堆算法inline void make_heap( b, e , cmp=greater<T>() )

该函数对[b,e)范围中的元素建立一个堆,所建的堆的类型由cmp决定,默认为大顶堆。

堆插入算法inline void push_heap(b,e,cmp=greater<T>() )

向堆中插入元素分为两个步骤:

(1)先将待插入的元素插入到底层容器的末端,通过push_back函数实现。

(2)再调用push_heap(b,e,cmp)函数堆新插入的元素做向上调整。

    所以,调用push_heap函数之前,先要保证待插入的元素已经放到了原容器的末尾,否则push_heap就做了无用功。

堆假删除算法inline void pop_heap(b,e,cmp=greater<T>() )

要实现堆的真正删除操作,分两步进行:

(1)先调用pop_heap函数将首部的元素与尾部元素交换,再将原尾部的元素做向下调整操作。此时,原堆顶元素被放置在最后一个位置,并未从底层容器中删除。

(2)若要实现真正的元素删除,可以调用底层容器的pop_back函数。

所以,在调用pop_heap函数后,若要实现元素真正从堆中删除,还需要调用底层容器的pop_back函数。

堆排序算法inline void sort_heap(b,e,cmp=greater<T>() )

根据上面的堆排序代码,我们可以看出,堆排序实际上是对堆中元素不断地假删除操作,只不过在删除过程中,[b,e)中的e每删除一次,就要做--e的更新

总结,以上的几个函数中,都要注意以下几个问题:

1.所以的[b,e)中的b和e都是随机访问迭代器(RandomAccessIterator),根据这一性质,我们可以得出,在我们之前提过的三个基础顺序容器中,只有vector和deque可以作为堆的底层实现容器,而list容器不能作为底层实现容器,因为list容器内置的迭代器为随机访问迭代器。

2.上述函数都含有第三个参数,该参数决定了堆的类型(大顶堆、小顶堆),默认情况下,该堆的类型为大顶堆。如果我们要使用小顶堆,则之后所有的以上函数的调用都必须加上对应的参数,否则结果就会出错。

3.以上函数的第三个参数,我们有两种给出实参的方法:

(1)采用仿函数对象,这是C++的习惯,对应的头文件为:#include<functional>。注意是仿函数对象,不是仿函数类型,对象只出现在函数内部,类型则只出现在模板内部仿函数都是inline函数,所以其效率要高于(2)

例如: 小顶堆------->greater<T>()     大顶堆------------------>less<T>()

(2)采用自定义函数名,并将该函数名传递给第三个参数,具体如下:

bool less_cmp(const int &a,const int &b)//等价于: less<int>()
{
	return a<b;
}

bool greater_cmp(const int &a, const int &b) //等价于: greater<int>()
{
	return a>b;
}

最后,我们给出一个小顶堆的测试程序及其输出结果:

#include<iostream>
#include<vector>
#include<list>
#include<algorithm>//后面用到copy函数和heap相关函数
#include<iterator>//后面用到迭代器ostream_iterator
#include<functional>//后面用到了一个比较的仿函数greater<T>
using namespace std;
typedef vector<int> Vint;
void print(const Vint& vec)//输出当前vector容器中的元素
{
	cout<<"容器内的元素为:";
	copy(vec.begin(),vec.end(),ostream_iterator<int>(cout," "));//将容器内的元素输出到标准输出设备上
	cout<<endl;
	cout<<"容器内元素的个数为:"<<vec.size()<<endl<<endl;;
}

bool cmp(const int &a,const int &b)
{
	return a>b;//大顶堆,则cmp相等于greater<int>(),注意不是greater<int>,前者是一个对象,后者是一个类
}
int main()
{
	int arr[]={3,2,1,9,4,12,15,7};
	vector<int>vec(arr,arr+sizeof(arr)/sizeof(int));//创建一个vector容器对象,将数组的副本压入到该容器中
	cout<<"-----------初始状态---------------"<<endl;
	print(vec);//将最初的vector容器的内容输出

	cout<<"-------------建堆----------------"<<endl;
	make_heap(vec.begin(),vec.end(),cmp);//新建一个小顶堆
	//上行代码等价于make_heap(vec.begin(),vec.end(),greater<int>()
	print(vec);

	cout<<"----------弹出堆顶元素-----------"<<endl;
	pop_heap(vec.begin(),vec.end(),cmp);//这里也要加cmp,因为弹出之后要给出向下调整的规则,否则系统会调用默认的最大堆调整方法
	print(vec);

	cout<<"--------向堆中插入值6的方法--------"<<endl;
	vec.push_back(6);//先将待插入的值放在容器的末尾
	push_heap(vec.begin(),vec.end(),cmp);//再最堆进行向下调整
	print(vec);

	cout<<"----------执行堆排序--------------"<<endl;
	sort_heap(vec.begin(),vec.end(),cmp);
	print(vec);
	return 0;
}

测试结果如下:

参考资料:

[1]《STL源码剖析 侯捷》

[2]《C++ primer 第4版》

STL之heap相关操作算法

时间: 2024-12-25 18:50:37

STL之heap相关操作算法的相关文章

数据结构(C语言版)链表相关操作算法的代码实现

这次实现的是带头结点的单链表的初始化.遍历.创建.插入.删除.判断链表是否为空.求链表长度函数,编译环境是vs2013. 其中插入和删除函数中循环的条件目前还不太明白. #include<iostream> using namespace std; typedef int Status; typedef char Elemtype; //定义链表的存储结构,注意这里与算法的定义有一处不同,是定义结构体时就命名Lnode,说明*next的类型只要说是Lnode类型就可以 typedef stru

数据结构-单链队列相关操作算法

#include <stdio.h>#include <stdlib.h> #define OVERFLOW -2#define OK 1#define ERROR 0 typedef int QElemType; //单链队列结构体定义typedef struct QNode {    QElemType data;    struct QNode *next;}QNode,*QueuePtr;typedef struct {    QueuePtr front;    Queu

STL -- heap结构及算法

STL -- heap结构及算法 heap(隐式表述,implicit representation) 1. heap概述 : vector + heap算法 heap并不归属于STL容器组件,它是个幕后英雄,扮演priority queue的助手.顾名思义,priority queue允许用户以任何次序将任何元素推入容器内,但取出时一定是从优先权最高(也就是数值最高)的元素开始取.binary max heap 正是具有这样的特性,适合作为priority queue 的底层机制. 让我们做一

STL C++ std::bind操作例子,仿函数操作配合算法库操作

1.stl::bind 和std::mem_fun_ref系列的配合使用出现了问题,多参形式不知道如何组织.适配器的操作真心难受!!!只能迷迷糊糊地用着.要使用非质变算法时需要作用于容器时只能考虑lambda或者transfer操作.待续 // functor-adapter_p431.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <algorithm>//元素操作算法 #include <functiona

c++中STL之heap, priority_queue使用

一.heap heap并不属于STL容器组件,它分为 max heap 和min heap,在缺省情况下,max-heap是优先队列(priority queue)的底层实现机制.而这个实现机制中的max-heap实际上是以一个vector表现的完全二叉树(complete binary tree).STL在<algorithm.h>中实现了对存储在vector/deque 中的元素进行堆操作的函数,包括make_heap, pop_heap, push_heap, sort_heap,对不愿

[转]一些NSArray,NSDictionary,NSSet相关的算法知识

iOS编程当中的几个集合类:NSArray,NSDictionary,NSSet以及对应的Mutable版本,应该所有人都用过.只是简单使用的话,相信没人会用错,但要做到高效(时间复杂度)精确(业务准确性),还需要了解其中所隐藏的算法知识. 在项目当中使用集合类几乎是不可避免的,集合类的使用场景其实可以进行抽象的归类.大多数时候我们需要将若干个对象(object)暂时保存起来,以备后续的业务逻辑进行操作,「保存和操作」,或者说「存与取」,对应到计算机世界的术语就是读和写.最初保存的时候我们Ins

Scala学习(三)----数组相关操作

数组相关操作 摘要: 本篇主要学习如何在Scala中操作数组.Java和C++程序员通常会选用数组或近似的结构(比如数组列表或向量)来收集一组元素.在Scala中,我们的选择更多,不过现在我们先假定不关心其他选择,而只是想马上开始用数组.本篇的要点包括: 1. 若长度固定则使用Array,若长度可能有变化则使用ArrayBuffer 2. 提供初始值时不要使用new 3. 用()来访问元素 4. 用for (elem<-arr)来遍历元素 5. 用for (elem<-arr if…)…yie

STL之heap

STL的堆操作 STL里面的堆操作一般用到的只有4个:make_heap();.pop_heap();.push_heap();.sort_heap();他们的头文件函数是#include <algorithm>首先是make_heap();他的函数原型是:void make_heap(first_pointer,end_pointer,compare_function);一个参数是数组或向量的头指针,第二个向量是尾指针.第三个参数是比较函数的名字.在缺省的时候,默认是大跟堆.(下面的参数都一

对vector等STL标准容器进行排序操作(转!)

西方有句谚语:不要重复发明轮子! STL几乎封装了所有的数据结构中的算法,从链表到队列,从向量到堆栈,对hash到二叉树,从搜索到排序,从增加到删除......可以说,如果你理解了STL,你会发现你已不用拘泥于算法本身,从而站在巨人的肩膀上去考虑更高级的应用. 排序是最广泛的算法之一,本文详细介绍了STL中不同排序算法的用法和区别. 1 STL提供的Sort 算法 C++之所以得到这么多人的喜欢,是因为它既具有面向对象的概念,又保持了C语言高效的特点.STL 排序算法同样需要保持高效.因此,对于