数据结构之(二叉)堆

(二叉)堆是一个数组,是一颗近似完全二叉树,分为大顶堆&小顶堆。表示堆的数组A有两个属性:(1)A.length表示数组元素的个数;(2)A.heap-size表示有多少个堆元素存储在数组A中。更多的关于堆的性质的介绍:算法导论第三版:p85-p89、编程珠玑:p141-p145。

堆的操作主要包括堆插入堆删除两个,而堆插入设计到SiftUp操作(自底向上调整),堆删除涉及到SiftDown操作(自顶向下调整,大顶堆时对应算法导论上的MAX-HEAPIFY操作)。

下面是大顶堆和小顶堆的基本操作的C++的实现:

//heap.h
#pragma once

template<class T>
class Heap
{
public:
	Heap(const int nmax = 100) : max_size(nmax)
	{
		arr = new T[max_size];
		size = 0;
	}
	virtual ~Heap() {delete []arr;}
	void Insert(const T &e);
	void Delete(const T &e);
	int Search(const T &e) const;
	void Print() const;
protected:
	T *arr;
	const int max_size;	//max array elements
	int size;	//size of heap
	virtual void SiftDown(int i) = 0;	//adjust from up to down
	virtual void SiftUp(int i) = 0;	//adjust from down to up
};

template<class T>
void Heap<T>::Insert(const T &e)
{
	if (size == max_size)
		return;
	arr[size++] = e;  //将新插入的元素添加在最后位置并增加size
	SiftUp(size - 1);  //数组下标从0开始
}

template<class T>
void Heap<T>::Delete(const T &e)
{
	int pos = Search(e);
	if (pos == -1)
		return;
	arr[pos] =	arr[--size];	//用最后的元素填充pos位置的元素,并减少size
	SiftDown(pos);
}

template<class T>
int Heap<T>::Search(const T &e)const
{
	for (int i = 0; i < size; i++)
		if (arr[i] == e)
			return i;
	return -1;
}

template<class T>
void Heap<T>::Print()const
{
	for (int i = 0; i < size; i++)
		cout << arr[i] << " ";
	cout << endl;
}

最大堆:maxHeap.h

//maxHeap.h
#pragma once
#include "heap.h"
#include <iostream>
using std::cout;
using std::endl;

template<class T>
class MaxHeap : public Heap<T>
{
public:
	MaxHeap(const int nmax = 100) : Heap(nmax) {}
	~MaxHeap() {}
private:
	virtual void SiftDown(int i);	//adjust from up to down
	virtual void SiftUp(int i);	//adjust from down to up
};

template<class T>
void MaxHeap<T>::SiftDown(int i)
{
	//已知arr[i,...,size)除arr[i]之外均满足大顶堆的定义,本函数向下调整arr[i]
	//使得在具有size个结点的堆中,以i为下标的根节点的子树重新遵循最大堆的性质
	//size为节点总数,从0开始计算,i节点的子节点为 2*i+1, 2*i+2
	int tmp, j;
	j = 2 * i + 1;//结点i的左孩子下标
	tmp = arr[i];
	while (j < size)
	{
		if (j + 1 < size && arr[j + 1] > arr[j])//找左右孩子中最大的
			j++;
		if (arr[j] <= tmp)	 //	父结点tmp=arr[i]比孩子结点arr[j]大
			break;
		arr[i] = arr[j];//把较大的子结点往上移动,替换它的父结点
		i = j;
		j = 2 * i + 1;
	}
	arr[i] = tmp;
}
template<class T>
void MaxHeap<T>::SiftUp(int i)
{
	//新加入结点i,原本arr[0, size)满足堆性质,向上调整使得[0, size+1)满足最大堆性质
	int j, tmp;
	j = (i - 1) / 2; //i的父结点
	tmp = arr[i];
	while (j >= 0 && i != 0)
	{
		if (arr[j] >= tmp)	//父节点arr[j]比子结点tmp = arr[i]大
			break;
		arr[i] = arr[j];//把较小的父结点往下移动,替换它的子结点
		i = j;
		j = (i - 1) / 2;
	}
	arr[i] = tmp;
}

最小堆:minHeap.h

//minHeap.h
#pragma once
#include "heap.h"
#include <iostream>
using std::cout;
using std::endl;

template<class T>
class MinHeap : public Heap<T>
{
public:
	MinHeap(const int max_size = 100) : Heap(max_size) {}
	~MinHeap(){}
private:
	virtual void SiftDown(int i);
	virtual void SiftUp(int i);
};

template<class T>
void MinHeap<T>::SiftDown(int i)
{
	//已知arr[i,...,size)除arr[i]之外均满足小顶堆的定义,本函数向下调整arr[i]
	//使得在具有size个结点的堆中,以i为下标的根节点的子树重新遵循最大堆的性质
	//size为节点总数,从0开始计算,i节点的子节点为 2*i+1, 2*i+2
	int j, tmp;
	j = 2 * i + 1;	//左孩子
	tmp = arr[i];
	while (j < size )
	{
		if (j + 1 < size && arr[j + 1] < arr[j])   //找左右孩子中最小的
			j++;
		if (arr[j] >= tmp) //	父结点tmp=arr[i]比孩子结点arr[j]小
			break;
		arr[i] = arr[j];  //把较小的子结点往上移动,替换它的父结点
		i = j;
		j = 2 * i + 1;
	}
	arr[i] = tmp;
}

template<class T>
void MinHeap<T>::SiftUp(int i)
{
	int j, tmp;
	j = (i - 1) / 2;  //父结点
	tmp = arr[i];
	while (j >= 0 && i != 0)
	{
		if (arr[j] <= tmp)	//父节点arr[j]比子结点tmp = arr[i]小
			break;
		arr[i] = arr[j];   //把较大的父结点往下移动,替换它的子结点
		i = j;
		j = (i - 1) / 2;
	}
	arr[i] = tmp;
}

测试代码:

#include "maxHeap.h"
#include "minHeap.h"

int main()
{
	int a[12] = {15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1};
	MaxHeap<int> maxheap(20);
	MinHeap<int> minheap(20);
	for (int i = 0; i < 12; i++)
		maxheap.Insert(a[i]);	 //向上调整
	maxheap.Print();
	maxheap.Delete(4);	 //向下调整
	maxheap.Print();

	for (int i = 0; i < 12; i++)
		minheap.Insert(a[i]);	 //向上调整
	minheap.Print();
	minheap.Delete(4);	 //向下调整
	minheap.Print();
	getchar();
	return 0;
}

这里用的是插入的方法建堆:最坏情况下,建立一个包含n个元素的堆的时间复杂度是O(nlgn),大theta。

下一篇文中的堆排序,可以在线性时间O(n)内,把一个无需数组构造成一个最大堆

一般地,还要k叉堆:算法导论第三版p93。

利用堆可以实现:堆排序优先队列。在Dijkstra算法中:
EXTRACT-MIN(Q) 操作,就可以先建立最小堆,然后从最小队列中,每次抽取最小结点(参考 最短路径之Dijkstra算法 一文的参考资料链接)。

此外,堆还可以用于:海量数据中选择前k个最大(最小)的数。

思想:在一个很大的无序数组里面选择前k个最大(最小)的数据,最直观的做法是把数组里面的数据全部排好序,然后输出前面最大(最小)的k个数据。但是,排序最好需要O(nlogn)的时间,而且我们不需要前k个最大(最小)的元素是有序的。这个时候我们可以建立k个元素的最小堆(得出前k个最大值)或者最大堆(得到前k个最小值),我们只需要遍历一遍数组,在把元素插入到堆中去只需要logk的时间,这个速度是很乐观的。利用堆得出前k个最大(最小)元素特别适合海量数据的处理。

参考资料:算法导论第三版:p85-p89、编程珠玑:p141-p145

数据结构之(二叉)堆

时间: 2024-10-17 16:28:41

数据结构之(二叉)堆的相关文章

D&amp;F学数据结构系列——二叉堆

二叉堆(binary heap) 二叉堆数据结构是一种数组对象,它可以被视为一棵完全二叉树.同二叉查找树一样,堆也有两个性质,即结构性和堆序性.对于数组中任意位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元2i+1中,它的父亲在[i/2](向下取整)中. 因此,一个数据结构将由一个数组.一个代表最大值的整数.以及当前的堆的大小组成.一个典型的优先队列(priority queue)如下: 1 #ifndef _BinHeap_H 2 struct HeapStruct; 3 type

优先队列 - 数据结构 (二叉堆)

优先队列包括二叉堆.d-堆.左式堆.斜堆.二项队列等 1.二叉堆 堆是一棵被完全填满的二叉树,有可能例外的是在底层,底层上的元素从左到右填入.这样的树称为完全二叉树. 堆序的性质:在一个堆中,对于每一个节点X,X的父亲的关键字小于(或等于)X中的关键字,根节点除外(它没有父节点).完全二叉树可以用数组实现. //关于二叉堆的头文件定义 如果要插入的元素是新的最小值,那么它将一直被推向堆顶.这样在某一个时刻,i将是1,我们就需要另Insert函数令程序跳出while循环,这个值必须保证小于或者至少

【数据结构】二叉堆

看到一篇很好的博文,来自http://blog.csdn.net/morewindows/article/details/6709644 下面是博文内容 堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先讲解下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是完全二叉树或者是近似完全二叉树. 二叉堆满足二个特性: 1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值. 2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆).

数据结构 之 二叉堆(Heap)

注:本节主要讨论最大堆(最小堆同理). 一.堆的概念 堆,又称二叉堆.同二叉查找树一样,堆也有两个性质,即结构性和堆序性. 1.结构性质: 堆是一棵被全然填满的二叉树.有可能的例外是在底层.底层上的元素从左到右填入.这种树称为全然二叉树(complete binary tree).下图就是这样一个样例. 对于全然二叉树,有这样一些性质: (1).一棵高h的全然二叉树,其包括2^h ~ (2^(h+1) - 1)个节点.也就是说.全然二叉树的高是[logN],显然它是O(logN). (2).全然

数据结构之二叉堆、堆排序

前言 上一篇写了数据结构之二叉搜索树.AVL自平衡树,这次来写堆. 堆的创造者 很久以前排序算法的时间复杂度一直是O(n^2), 当时学术界充斥着"排序算法不可能突破O(n^2)"的声音,直到1959年,由D.L.Shell提出了一种排序算法,希尔排序(Shell Sort),才打破了这种不可能的声音,把排序算法的时间复杂度提升到了O(n^3/2)! 当科学家们知道这种"不可能"被突破之后,又相继有了更快的排序算法,"不可能超越O(n^2)"彻底

数据结构学习——二叉堆ADT(程序化)

参考书籍<数据结构与算法分析--C语言描述> 关于堆的一些基本概念,可参见小zz的另一篇博文. /*本例程实现的是最小堆,最大堆类似*/ #include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<string.h> #define MAX 13 typedef struct BHeap { int Capacity;//堆的最大容量 int Size;//当前堆大小 int

数据结构之二叉堆(构建堆,堆排序)-(七)

/* * @author Lip * @date 2015-04-23 */ public class Heap { public static void main(String[] args) { // TODO Auto-generated method stub //System.out.println((int)-1.5); int []array={4,2,7,9,3,6,1,12,10,5}; System.out.println("原始:"); printHeapByLe

【笔记】【数据结构】二叉堆

作用: 插入元素,O(lgN) 修改元素,O(lgN) 删除元素,O(lgN) 查询元素,O(1) 动态查询最值,O(NlgN)-O(lgN)-O(1) 核心操作: 上浮与下沉 最小堆:上浮是指较小值上浮,下沉是指较大值下沉. 最大堆:上浮是指较大值上浮,下沉是指较小值下沉. 具体操作: 预处理中,对所有的根节点下沉操作,即交换根节点与一个较小的子节点,然后接着将子节点作为根节点,进行下一次下沉操作. 插入元素时,将它放入最底层,并不断地上浮. 删除堆顶元素时,将堆顶元素与最后一个元素交换,然后

POJ 2010 - Moo University - Financial Aid 初探数据结构 二叉堆

考虑到数据结构短板严重,从计算几何换换口味= = 二叉堆 简介 堆总保持每个节点小于(大于)父亲节点.这样的堆被称作大根堆(小根堆). 顾名思义,大根堆的数根是堆内的最大元素. 堆的意义在于能快速O(1)找到最大/最小值,并能持续维护. 复杂度 push() = O(logn); pop() = O(logn); BinaryHeap() = O(nlogn); 实现 数组下标从1开始的情况下,有 Parent(i) = i >> 1 LChild(i) = i << 1 RChi

《数据结构与算法分析:C语言描述》复习——第五章“堆”——二叉堆

2014.06.15 22:14 简介: 堆是一种非常实用的数据结构,其中以二叉堆最为常用.二叉堆可以看作一棵完全二叉树,每个节点的键值都大于(小于)其子节点,但左右孩子之间不需要有序.我们关心的通常只有堆顶的元素,而整个堆则被封装起来,保存在一个数组中. 图示: 下图是一个最大堆: 实现: 优先队列是STL中最常用的工具之一,许多算法的优化都要利用堆,使用的工具就是优先队列.STL中的优先队列通过仿函数来定义比较算法,此处我偷懒用了“<”运算符.关于使用仿函数的好处,我之后如果有时间深入学习S